+1
.gitignore
+1
.gitignore
+4
-2
Cargo.lock
+4
-2
Cargo.lock
···
254
254
[[package]]
255
255
name = "cid"
256
256
version = "0.11.1"
257
-
source = "git+https://github.com/edouardparis/rust-cid?branch=fix-dep-serde_bytes-features#d8aeda78e2dd4784dbea3086a6ca85fa88782d2d"
257
+
source = "git+https://github.com/multiformats/rust-cid?branch=master#6a13cb931d3237e5e1f5635a943edfd166e2e78c"
258
258
dependencies = [
259
259
"core2",
260
260
"multibase",
···
1265
1265
[[package]]
1266
1266
name = "serde_ipld_dagcbor"
1267
1267
version = "0.6.3"
1268
-
source = "git+http://github.com/edouardparis/serde_ipld_dagcbor?branch=scopeguard-no-default-features#82eca93ee126750ad80feedbfe9cee0bf162c587"
1268
+
source = "git+http://github.com/ipld/serde_ipld_dagcbor?branch=master#d7f93d06ce6a19867abeea78d02d6ded6c476b81"
1269
1269
dependencies = [
1270
1270
"cbor4ii",
1271
1271
"ipld-core",
···
1612
1612
version = "0.0.1"
1613
1613
dependencies = [
1614
1614
"base58",
1615
+
"cid",
1615
1616
"hex-literal",
1616
1617
"k256",
1617
1618
"postcard",
···
1628
1629
"base32",
1629
1630
"base58",
1630
1631
"base64",
1632
+
"cid",
1631
1633
"clap",
1632
1634
"hidapi",
1633
1635
"ledger-transport-hid",
+3
-2
Cargo.toml
+3
-2
Cargo.toml
···
11
11
12
12
[dependencies]
13
13
k256 = { version = "0.13.4", default-features = false, features = ["alloc", "ecdsa-core"] }
14
+
cid = { version = "0.11.1", default-features = false, features = ['alloc'] }
14
15
postcard = { version = "1.1.1", features = ["alloc"] }
15
-
serde_ipld_dagcbor = { git = "http://github.com/edouardparis/serde_ipld_dagcbor", branch = "scopeguard-no-default-features", default-features = false }
16
+
serde_ipld_dagcbor = { git = "http://github.com/ipld/serde_ipld_dagcbor", branch = "master", default-features = false }
16
17
sdk = { package = "vanadium-app-sdk", git = "https://github.com/LedgerHQ/vanadium"}
17
18
common = { package = "vnd-atproto-common", path = "common" }
18
19
base58 = "0.2.0"
19
20
serde = { version = "1.0", default-features = false, features = ["alloc"] }
20
21
21
22
[patch.crates-io]
22
-
cid = { git = "https://github.com/edouardparis/rust-cid", branch = "fix-dep-serde_bytes-features" }
23
+
cid = { git = "https://github.com/multiformats/rust-cid", branch = "master" }
23
24
24
25
[dev-dependencies]
25
26
hex-literal = "0.4.1"
+2
-1
cli/Cargo.toml
+2
-1
cli/Cargo.toml
···
14
14
shellwords = "1.1.0"
15
15
hidapi = "2.6.3"
16
16
ledger-transport-hid = "0.11.0"
17
-
serde_ipld_dagcbor = { git = "http://github.com/edouardparis/serde_ipld_dagcbor", branch = "scopeguard-no-default-features", default-features = false }
17
+
cid = { version = "0.11.1", default-features = false }
18
+
serde_ipld_dagcbor = { git = "http://github.com/ipld/serde_ipld_dagcbor", branch = "master", default-features = false }
18
19
serde = { version = "1.0", default-features = false, features = ["alloc", "derive"] }
19
20
serde_json = { version = "1.0", default-features = false, features = ["alloc"] }
20
21
tokio = { version = "1.38.1", features = ["io-util", "macros", "net", "rt", "rt-multi-thread", "sync"] }
+20
cli/previous_operation.json
+20
cli/previous_operation.json
···
1
+
{
2
+
"alsoKnownAs": [
3
+
"at://test-vnd.edouard.paris"
4
+
],
5
+
"prev": null,
6
+
"rotationKeys": [
7
+
"did:key:zQ3shiNS7Bm7F3AH5NU4uimiSpZ8ryYj365Uq1N7KMsWUaXKp"
8
+
],
9
+
"services": {
10
+
"atproto_pds": {
11
+
"endpoint": "https://pds.edouard.paris",
12
+
"type": "AtprotoPersonalDataServer"
13
+
}
14
+
},
15
+
"sig": "kfShJ7A67jZAjZhydcnz3HRnrlE0NqocV2RmjRNlCB5hyj4Qiw51Kn_Bkqj704KYOTEiyhpiVajRc-qVc9ssWw",
16
+
"type": "plc_operation",
17
+
"verificationMethods": {
18
+
"atproto": "did:key:zQ3shiNS7Bm7F3AH5NU4uimiSpZ8ryYj365Uq1N7KMsWUaXKp"
19
+
}
20
+
}
+24
-15
cli/src/main.rs
+24
-15
cli/src/main.rs
···
1
1
use base58::ToBase58;
2
2
use base64::Engine;
3
+
use cid::multihash;
3
4
use clap::{CommandFactory, Parser, Subcommand};
4
5
use rustyline::completion::{Completer, Pair};
5
6
use rustyline::error::ReadlineError;
···
18
19
AtprotoAppClient,
19
20
};
20
21
21
-
use serde::{Deserialize, Serialize};
22
22
use std::borrow::Cow;
23
23
use std::fs::File;
24
24
use std::io::Read;
···
51
51
previous: Option<String>,
52
52
#[clap(long, default_missing_value = "true", num_args = 0..=1)]
53
53
new_plc_did: bool,
54
+
},
55
+
GetCID {
56
+
/// Path to operation json file
57
+
#[clap(long)]
58
+
operation: String,
54
59
},
55
60
Exit,
56
61
}
···
209
214
previous,
210
215
new_plc_did,
211
216
} => {
212
-
let operation = read_operation_file(&operation)?;
213
-
let previous = if let Some(path) = previous {
214
-
Some(read_operation_file(&path)?)
217
+
let operation: client::PlcOperation = read_json_file(&operation)?;
218
+
let previous: Option<client::SignedPlcOperation> = if let Some(path) = previous {
219
+
Some(read_json_file(&path)?)
215
220
} else {
216
221
None
217
222
};
···
221
226
.map(|s| base64::prelude::BASE64_URL_SAFE_NO_PAD.encode(s))?;
222
227
println!("sig: {}", sig);
223
228
if *new_plc_did {
224
-
let b = serde_ipld_dagcbor::to_vec(&SignedPlcOperation { operation, sig }).unwrap();
229
+
let b = serde_ipld_dagcbor::to_vec(&operation.signed(sig)).unwrap();
225
230
let hash = sha2::Sha256::digest(b);
226
231
let s = base32::encode(base32::Alphabet::Rfc4648Lower { padding: true }, &hash);
227
232
eprintln!("did:plc:{}", &s[..24]);
228
233
}
234
+
}
235
+
CliCommand::GetCID { operation } => {
236
+
let operation: client::SignedPlcOperation = read_json_file(&operation)?;
237
+
let b = serde_ipld_dagcbor::to_vec(&operation).unwrap();
238
+
let digest = sha2::Sha256::digest(b);
239
+
let wrap = multihash::Multihash::wrap(0x12, &digest).unwrap();
240
+
let cid = cid::Cid::new(cid::Version::V1, 0x71, wrap).unwrap();
241
+
eprintln!("cid: {}", cid);
229
242
}
230
243
CliCommand::Exit => {
231
244
app_client.exit().await?;
···
235
248
Ok(())
236
249
}
237
250
238
-
#[derive(Serialize)]
239
-
pub struct SignedPlcOperation {
240
-
#[serde(flatten)]
241
-
operation: client::PlcOperation,
242
-
sig: String,
243
-
}
244
-
245
-
fn read_operation_file(path: &str) -> Result<client::PlcOperation, Box<dyn std::error::Error>> {
251
+
fn read_json_file<T>(path: &str) -> Result<T, Box<dyn std::error::Error>>
252
+
where
253
+
T: serde::de::DeserializeOwned,
254
+
{
246
255
let mut file = File::open(path)?;
247
256
let mut contents = String::new();
248
257
file.read_to_string(&mut contents)?;
249
-
let operation: client::PlcOperation = serde_json::from_str(&contents)?;
250
-
Ok(operation)
258
+
let data: T = serde_json::from_str(&contents)?;
259
+
Ok(data)
251
260
}
252
261
253
262
#[tokio::main(flavor = "multi_thread")]
+1
-1
client/Cargo.toml
+1
-1
client/Cargo.toml
···
7
7
postcard = { version = "1.1.1", features = ["alloc"] }
8
8
common = { package = "vnd-atproto-common", path = "../common"}
9
9
sdk = { package = "vanadium-client-sdk", git = "https://github.com/LedgerHQ/vanadium"}
10
-
serde_ipld_dagcbor = { git = "http://github.com/edouardparis/serde_ipld_dagcbor", branch = "scopeguard-no-default-features", default-features = false }
10
+
serde_ipld_dagcbor = { git = "http://github.com/ipld/serde_ipld_dagcbor", branch = "master", default-features = false }
+4
-3
client/src/lib.rs
+4
-3
client/src/lib.rs
···
1
-
pub use common::message::PlcOperation;
1
+
pub use common::message::{PlcOperation, SignedPlcOperation};
2
2
use common::message::{Request, Response};
3
3
use sdk::vanadium_client::{VAppClient, VAppExecutionError};
4
4
···
94
94
&mut self,
95
95
key_index: u32,
96
96
operation: PlcOperation,
97
-
previous: Option<PlcOperation>,
97
+
previous: Option<SignedPlcOperation>,
98
98
) -> Result<Vec<u8>, AtprotoAppClientError> {
99
99
let msg = postcard::to_allocvec(&Request::SignPlcOperation {
100
100
key_index,
101
101
previous,
102
102
operation,
103
103
})
104
-
.map_err(|_| {
104
+
.map_err(|e| {
105
+
eprintln!("{:?}", e);
105
106
AtprotoAppClientError::GenericError("Failed to serialize SignPlcOperation request")
106
107
})?;
107
108
+27
-1
common/src/message.rs
+27
-1
common/src/message.rs
···
11
11
},
12
12
SignPlcOperation {
13
13
key_index: u32,
14
-
previous: Option<PlcOperation>,
15
14
operation: PlcOperation,
15
+
previous: Option<SignedPlcOperation>,
16
16
},
17
17
}
18
18
···
25
25
pub also_known_as: Vec<String>,
26
26
pub services: Services,
27
27
pub prev: Option<String>,
28
+
}
29
+
30
+
impl PlcOperation {
31
+
pub fn signed(self, sig: String) -> SignedPlcOperation {
32
+
SignedPlcOperation {
33
+
r#type: self.r#type,
34
+
rotation_keys: self.rotation_keys,
35
+
verification_methods: self.verification_methods,
36
+
also_known_as: self.also_known_as,
37
+
services: self.services,
38
+
prev: self.prev,
39
+
sig,
40
+
}
41
+
}
42
+
}
43
+
44
+
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
45
+
#[serde(rename_all = "camelCase")]
46
+
pub struct SignedPlcOperation {
47
+
pub r#type: String,
48
+
pub rotation_keys: Vec<String>,
49
+
pub verification_methods: VerificationMethods,
50
+
pub also_known_as: Vec<String>,
51
+
pub services: Services,
52
+
pub prev: Option<String>,
53
+
pub sig: String,
28
54
}
29
55
30
56
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
+95
-10
src/main.rs
+95
-10
src/main.rs
···
7
7
vec::Vec,
8
8
};
9
9
use base58::ToBase58;
10
-
use common::message::{PlcOperation, Request, Response};
10
+
use common::message::{PlcOperation, Request, Response, SignedPlcOperation};
11
11
use sdk::{
12
12
App,
13
13
curve::{Curve, EcfpPrivateKey, EcfpPublicKey, Secp256k1, ToPublicKey},
···
84
84
}
85
85
86
86
#[cfg(not(test))]
87
-
fn operation_tags(operation: &PlcOperation) -> Vec<sdk::ux::TagValue> {
87
+
fn all_operation_tags(operation: &PlcOperation) -> Vec<sdk::ux::TagValue> {
88
88
use sdk::ux::TagValue;
89
89
let mut tags: Vec<TagValue> = operation
90
90
.rotation_keys
···
116
116
}
117
117
118
118
#[cfg(not(test))]
119
-
fn display_full_operation(pubkey: &PublicDidKey, index: u32, operation: &PlcOperation) -> bool {
119
+
fn 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))]
164
+
fn display_partial_operation(
165
+
pubkey: &PublicDidKey,
166
+
index: u32,
167
+
operation: &PlcOperation,
168
+
previous: &SignedPlcOperation,
169
+
) -> bool {
120
170
sdk::ux::review_pairs(
121
171
"Sign plc operation",
122
172
"",
123
-
&operation_tags(operation),
173
+
&changed_operation_tags(operation, previous),
124
174
&alloc::format!("with key #{} {} ", index, pubkey.to_string()),
125
175
"Confirm",
126
176
false,
···
128
178
}
129
179
130
180
#[cfg(test)]
181
+
fn 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))]
131
191
fn 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)]
203
+
fn display_full_operation(_pubkey: &PublicDidKey, _index: u32, _operation: &PlcOperation) -> bool {
132
204
true
133
205
}
134
206
135
207
pub fn sign_plc_operation(
136
208
key_index: u32,
137
209
operation: PlcOperation,
138
-
previous: Option<PlcOperation>,
210
+
previous: Option<SignedPlcOperation>,
139
211
) -> Result<Response, &'static str> {
140
212
if key_index > 256 {
141
213
return Err("Index is too long");
···
149
221
let privkey: EcfpPrivateKey<Secp256k1, 32> = EcfpPrivateKey::new(*hd_node.privkey);
150
222
let did_key = PublicDidKey::new(&privkey.to_public_key());
151
223
152
-
if let Some(operation) = previous {
153
-
if !operation.rotation_keys.contains(&did_key.to_string()) {
224
+
if let Some(previous) = previous {
225
+
if !previous.rotation_keys.contains(&did_key.to_string()) {
154
226
return Err("Key is not part of the previous operation rotation_keys");
155
227
}
156
-
// todo: check previous
157
-
}
158
228
159
-
if !display_full_operation(&did_key, key_index, &operation) {
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) {
160
245
return Err("Rejected by the user");
161
246
}
162
247