+2
CLAUDE.prompts.md
+2
CLAUDE.prompts.md
···
24
24
25
25
Write a project `README.md` file that describes the project as a library that supports ATProtocol identity record signing and verifying. Note that parts of this was extracted from the open sourced https://tangled.sh/@smokesignal.events/smokesignal project. This project is open source under the MIT license.
26
26
27
+
The `REAADME.md` file should provide a high level overview of both the `atproto-identity` and `atproto-record` crates. It should also concisely reference the available binaries and provide a minimal example of how to use them.
28
+
27
29
## Check and clippy
28
30
29
31
Using `cargo clippy`, satisfy warnings. Think very hard about how to do this.
+9
LICENSE
+9
LICENSE
···
1
+
MIT License
2
+
3
+
Copyright (c) 2024 Nick Gerakines
4
+
5
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+220
README.md
+220
README.md
···
1
+
# AT Protocol Identity & Record Library
2
+
3
+
A comprehensive Rust library for AT Protocol identity management and record signing/verification. This library provides full functionality for DID resolution, handle resolution, identity document management, and cryptographic record operations across multiple DID methods.
4
+
5
+
Parts of this library were extracted from the open-sourced [Smokesignal](https://tangled.sh/@smokesignal.events/smokesignal) project.
6
+
7
+
## Crates
8
+
9
+
This workspace contains two main crates:
10
+
11
+
### `atproto-identity`
12
+
13
+
A comprehensive AT Protocol identity management library providing:
14
+
15
+
- **DID Resolution**: Support for `did:plc`, `did:web`, and `did:key` methods
16
+
- **Handle Resolution**: DNS-based handle resolution with validation
17
+
- **Identity Documents**: Complete DID document parsing and management
18
+
- **Cryptographic Operations**: P-256 and K-256 elliptic curve support
19
+
- **Validation**: Input validation for handles and DIDs
20
+
- **Configuration**: Environment-based configuration management
21
+
22
+
### `atproto-record`
23
+
24
+
AT Protocol record signature operations library providing:
25
+
26
+
- **Record Signing**: Create cryptographic signatures for AT Protocol records
27
+
- **Signature Verification**: Verify existing signatures against records and public keys
28
+
- **IPLD Integration**: Proper IPLD DAG-CBOR serialization for signature content
29
+
- **Multi-curve Support**: Support for P-256 and K-256 elliptic curves
30
+
31
+
## CLI Tools
32
+
33
+
The library includes several command-line utilities:
34
+
35
+
### atproto-identity
36
+
37
+
- `atproto-identity-resolve` - Resolve DIDs and handles to identity documents
38
+
- `atproto-identity-sign` - Sign identity-related operations
39
+
- `atproto-identity-validate` - Validate DID and handle formats
40
+
41
+
### atproto-record
42
+
43
+
- `atproto-record-sign` - Sign AT Protocol records from files or stdin
44
+
- `atproto-record-verify` - Verify AT Protocol record signatures from files or stdin
45
+
46
+
## Quick Start
47
+
48
+
Add the crates to your `Cargo.toml`:
49
+
50
+
```toml
51
+
[dependencies]
52
+
atproto-identity = "0.3.0"
53
+
atproto-record = "0.3.0"
54
+
```
55
+
56
+
### Basic Identity Resolution
57
+
58
+
```rust
59
+
use atproto_identity::resolve::resolve_handle;
60
+
61
+
#[tokio::main]
62
+
async fn main() -> anyhow::Result<()> {
63
+
// Resolve a handle to a DID
64
+
let did = resolve_handle("alice.bsky.social").await?;
65
+
println!("Resolved DID: {}", did);
66
+
67
+
Ok(())
68
+
}
69
+
```
70
+
71
+
### Record Signing and Verification
72
+
73
+
```rust
74
+
use atproto_identity::key::identify_key;
75
+
use atproto_record::signature;
76
+
use serde_json::json;
77
+
78
+
#[tokio::main]
79
+
async fn main() -> anyhow::Result<()> {
80
+
// Parse DID key for signing operations
81
+
let signing_key = identify_key("did:key:z42tv1pb3Dzog28Q1udyieg1YJP3x1Un5vraE1bttXeCDSpW")?;
82
+
83
+
// Create a record to sign
84
+
let record = json!({
85
+
"$type": "app.bsky.feed.post",
86
+
"text": "Hello AT Protocol!",
87
+
"createdAt": "2024-01-01T00:00:00.000Z"
88
+
});
89
+
90
+
// Create signature object with issuer and timestamp
91
+
let signature_object = json!({
92
+
"issuer": "did:plc:tgudj2fjm77pzkuawquqhsxm",
93
+
"issued_at": "2024-01-01T00:00:00.000Z"
94
+
});
95
+
96
+
// Sign the record
97
+
let signed_record = signature::create(
98
+
&signing_key,
99
+
&record,
100
+
"did:plc:4zutorghlchjxzgceklue4la", // repository
101
+
"app.bsky.feed.post", // collection
102
+
signature_object,
103
+
).await?;
104
+
105
+
// Verify the signature
106
+
signature::verify(
107
+
"did:plc:tgudj2fjm77pzkuawquqhsxm", // issuer
108
+
&signing_key, // verification key
109
+
signed_record, // signed record
110
+
"did:plc:4zutorghlchjxzgceklue4la", // repository
111
+
"app.bsky.feed.post", // collection
112
+
).await?;
113
+
114
+
println!("Signature verification successful");
115
+
116
+
Ok(())
117
+
}
118
+
```
119
+
120
+
### CLI Usage Examples
121
+
122
+
```bash
123
+
# Resolve a handle or DID
124
+
cargo run --bin atproto-identity-resolve -- alice.bsky.social
125
+
126
+
# Get full DID document
127
+
cargo run --bin atproto-identity-resolve -- --did-document did:plc:abc123
128
+
129
+
# Sign a record from file
130
+
cargo run --bin atproto-record-sign -- \
131
+
did:key:z42tv1pb3Dzog28Q1udyieg1YJP3x1Un5vraE1bttXeCDSpW \
132
+
./record.json \
133
+
did:plc:tgudj2fjm77pzkuawquqhsxm \
134
+
repository=did:plc:4zutorghlchjxzgceklue4la \
135
+
collection=app.bsky.feed.post
136
+
137
+
# Sign a record from stdin
138
+
echo '{"$type":"app.bsky.feed.post","text":"Hello!"}' | \
139
+
cargo run --bin atproto-record-sign -- \
140
+
did:key:z42tv1pb3Dzog28Q1udyieg1YJP3x1Un5vraE1bttXeCDSpW \
141
+
-- \
142
+
did:plc:tgudj2fjm77pzkuawquqhsxm \
143
+
repository=did:plc:4zutorghlchjxzgceklue4la \
144
+
collection=app.bsky.feed.post
145
+
146
+
# Verify a signature from file
147
+
cargo run --bin atproto-record-verify -- \
148
+
did:plc:tgudj2fjm77pzkuawquqhsxm \
149
+
did:key:z42tv1pb3Dzog28Q1udyieg1YJP3x1Un5vraE1bttXeCDSpW \
150
+
./signed_record.json \
151
+
repository=did:plc:4zutorghlchjxzgceklue4la \
152
+
collection=app.bsky.feed.post
153
+
154
+
# Verify a signature from stdin
155
+
echo '{"signatures":[...],"text":"Hello!"}' | \
156
+
cargo run --bin atproto-record-verify -- \
157
+
did:plc:tgudj2fjm77pzkuawquqhsxm \
158
+
did:key:z42tv1pb3Dzog28Q1udyieg1YJP3x1Un5vraE1bttXeCDSpW \
159
+
-- \
160
+
repository=did:plc:4zutorghlchjxzgceklue4la \
161
+
collection=app.bsky.feed.post
162
+
```
163
+
164
+
## Development
165
+
166
+
### Building
167
+
168
+
```bash
169
+
cargo build
170
+
```
171
+
172
+
### Running Tests
173
+
174
+
```bash
175
+
cargo test
176
+
```
177
+
178
+
### Code Quality
179
+
180
+
```bash
181
+
# Format code
182
+
cargo fmt
183
+
184
+
# Lint
185
+
cargo clippy
186
+
187
+
# Check without building
188
+
cargo check
189
+
```
190
+
191
+
## Features
192
+
193
+
- **Async/Await**: Built with modern Rust async patterns using Tokio
194
+
- **Error Handling**: Comprehensive structured error types using `thiserror`
195
+
- **Logging**: Structured logging with `tracing`
196
+
- **Security**: Forbids unsafe code and follows security best practices
197
+
- **Standards Compliance**: Full AT Protocol specification compliance
198
+
- **Multi-platform**: Works on all major platforms
199
+
200
+
## Architecture
201
+
202
+
The library follows a modular architecture with clear separation of concerns:
203
+
204
+
- **Identity Management**: Handle DID resolution, validation, and document management
205
+
- **Cryptographic Operations**: Secure key operations and signature handling
206
+
- **Network Operations**: HTTP and DNS resolution with proper error handling
207
+
- **Data Models**: Comprehensive type definitions for AT Protocol entities
208
+
- **CLI Tools**: Ready-to-use command-line utilities
209
+
210
+
## License
211
+
212
+
MIT License - see [LICENSE](LICENSE) for details.
213
+
214
+
## Contributing
215
+
216
+
Contributions are welcome! This project follows standard Rust conventions and includes comprehensive testing and documentation requirements.
217
+
218
+
## Repository
219
+
220
+
https://tangled.sh/@smokesignal.events/atproto-identity-rs
+47
-9
crates/atproto-record/src/bin/atproto-record-sign.rs
+47
-9
crates/atproto-record/src/bin/atproto-record-sign.rs
···
6
6
use atproto_record::signature::create;
7
7
use chrono::{SecondsFormat, Utc};
8
8
use serde_json::json;
9
-
use std::{collections::HashMap, env, fs};
9
+
use std::{
10
+
collections::HashMap,
11
+
env, fs,
12
+
io::{self, Read},
13
+
};
10
14
11
15
/// AT Protocol Record Signing Tool
12
16
///
13
17
/// This command-line tool provides cryptographic signing capabilities for AT Protocol records.
14
-
/// It reads a JSON record from a file, applies a cryptographic signature using a DID key,
18
+
/// It reads a JSON record from a file or stdin, applies a cryptographic signature using a DID key,
15
19
/// and outputs the signed record with embedded signature metadata.
16
20
///
17
21
/// ## Overview
···
19
23
/// The tool performs the following operations:
20
24
/// 1. **Command Line Parsing**: Extracts signing parameters from command line arguments
21
25
/// 2. **Key Resolution**: Converts DID key strings to cryptographic key material
22
-
/// 3. **Record Loading**: Reads and parses JSON records from disk files
26
+
/// 3. **Record Loading**: Reads and parses JSON records from disk files or stdin
23
27
/// 4. **Signature Creation**: Generates cryptographic signatures using IPLD DAG-CBOR serialization
24
28
/// 5. **Output Generation**: Produces signed records with embedded signature objects
25
29
///
···
37
41
/// The tool accepts flexible argument ordering:
38
42
/// - **DID Key** (`did:key:...`) - Cryptographic key for signing operations
39
43
/// - **Issuer DID** (`did:plc:...` or `did:web:...`) - Identity of the signature issuer
40
-
/// - **Record File** (file path) - JSON file containing the record to sign
44
+
/// - **Record Input** (file path or `--`) - JSON file containing the record to sign, or `--` to read from stdin
41
45
/// - **Parameters** (`key=value`) - Repository, collection, and signature metadata
42
46
///
43
47
/// ## Required Parameters
···
73
77
/// issued_at=2025-05-16T14:00:02.000Z
74
78
/// ```
75
79
///
80
+
/// ### Reading from Stdin
81
+
/// ```bash
82
+
/// echo '{"$type":"app.bsky.feed.post","text":"Hello from stdin!"}' | \
83
+
/// atproto-record-sign \
84
+
/// did:key:z42tv1pb3Dzog28Q1udyieg1YJP3x1Un5vraE1bttXeCDSpW \
85
+
/// -- \
86
+
/// did:plc:tgudj2fjm77pzkuawquqhsxm \
87
+
/// repository=did:plc:4zutorghlchjxzgceklue4la \
88
+
/// collection=app.bsky.feed.post
89
+
/// ```
90
+
///
76
91
/// ### Input Record Example (`post.json`)
77
92
/// ```json
78
93
/// {
···
121
136
println!("AT Protocol Record Signing Tool");
122
137
println!();
123
138
println!("USAGE:");
124
-
println!(" atproto-record-sign <ISSUER_DID> <SIGNING_KEY> <RECORD_FILE> repository=<REPO> collection=<COLLECTION> [key=value...]");
139
+
println!(" atproto-record-sign <ISSUER_DID> <SIGNING_KEY> <RECORD_INPUT> repository=<REPO> collection=<COLLECTION> [key=value...]");
125
140
println!();
126
141
println!("ARGUMENTS:");
127
-
println!(" <ISSUER_DID> DID of the issuer (e.g., did:plc:...)");
128
-
println!(" <SIGNING_KEY> DID key for signing (e.g., did:key:z42tv1...)");
129
-
println!(" <RECORD_FILE> Path to JSON file containing the record to sign");
142
+
println!(" <ISSUER_DID> DID of the issuer (e.g., did:plc:...)");
143
+
println!(" <SIGNING_KEY> DID key for signing (e.g., did:key:z42tv1...)");
144
+
println!(" <RECORD_INPUT> Path to JSON file containing the record to sign, or '--' to read from stdin");
130
145
println!();
131
146
println!("REQUIRED PARAMETERS:");
132
147
println!(" repository=<REPO> Repository DID context");
···
136
151
println!(" issued_at=<TIMESTAMP> RFC 3339 timestamp (defaults to current time)");
137
152
println!(" [key=value...] Additional fields for signature object");
138
153
println!();
139
-
println!("EXAMPLE:");
154
+
println!("EXAMPLES:");
155
+
println!(" # Sign from file:");
140
156
println!(" atproto-record-sign \\");
141
157
println!(" did:key:z42tv1pb3Dzog28Q1udyieg1YJP3x1Un5vraE1bttXeCDSpW \\");
142
158
println!(" ./record.json \\");
143
159
println!(" did:plc:tgudj2fjm77pzkuawquqhsxm \\");
144
160
println!(" repository=did:plc:4zutorghlchjxzgceklue4la \\");
145
161
println!(" collection=community.lexicon.badge.award");
162
+
println!();
163
+
println!(" # Sign from stdin:");
164
+
println!(" echo '{{\"$type\":\"app.bsky.feed.post\",\"text\":\"Hello!\"}}' | atproto-record-sign \\");
165
+
println!(" did:key:z42tv1pb3Dzog28Q1udyieg1YJP3x1Un5vraE1bttXeCDSpW \\");
166
+
println!(" -- \\");
167
+
println!(" did:plc:tgudj2fjm77pzkuawquqhsxm \\");
168
+
println!(" repository=did:plc:4zutorghlchjxzgceklue4la \\");
169
+
println!(" collection=app.bsky.feed.post");
146
170
return Ok(());
147
171
}
148
172
···
178
202
}
179
203
Ok(_) => return Err(anyhow!("Unsupported DID method: {}", argument)),
180
204
Err(e) => return Err(anyhow!("Failed to parse DID {}: {}", argument, e)),
205
+
}
206
+
} else if argument == "--" {
207
+
// Read record from stdin
208
+
if record.is_none() {
209
+
let mut stdin_content = String::new();
210
+
io::stdin()
211
+
.read_to_string(&mut stdin_content)
212
+
.map_err(|e| anyhow!("Failed to read from stdin: {}", e))?;
213
+
record = Some(
214
+
serde_json::from_str(&stdin_content)
215
+
.map_err(|e| anyhow!("Failed to parse JSON from stdin: {}", e))?,
216
+
);
217
+
} else {
218
+
return Err(anyhow!("Unexpected argument: {}", argument));
181
219
}
182
220
} else {
183
221
// Assume it's a file path to read the record from
+47
-10
crates/atproto-record/src/bin/atproto-record-verify.rs
+47
-10
crates/atproto-record/src/bin/atproto-record-verify.rs
···
4
4
resolve::{parse_input, InputType},
5
5
};
6
6
use atproto_record::signature::verify;
7
-
use std::{env, fs};
7
+
use std::{
8
+
env, fs,
9
+
io::{self, Read},
10
+
};
8
11
9
12
/// AT Protocol Record Verification Tool
10
13
///
11
14
/// This command-line tool provides cryptographic signature verification capabilities for AT Protocol records.
12
-
/// It reads a signed JSON record from a file, validates the embedded cryptographic signatures using a public key,
15
+
/// It reads a signed JSON record from a file or stdin, validates the embedded cryptographic signatures using a public key,
13
16
/// and reports whether the signature verification succeeds or fails.
14
17
///
15
18
/// ## Overview
···
17
20
/// The tool performs the following operations:
18
21
/// 1. **Command Line Parsing**: Extracts verification parameters from command line arguments
19
22
/// 2. **Key Resolution**: Converts DID key strings to cryptographic key material for verification
20
-
/// 3. **Record Loading**: Reads and parses signed JSON records from disk files
23
+
/// 3. **Record Loading**: Reads and parses signed JSON records from disk files or stdin
21
24
/// 4. **Signature Verification**: Validates cryptographic signatures using IPLD DAG-CBOR deserialization
22
25
/// 5. **Result Reporting**: Outputs verification success or detailed failure information
23
26
///
···
37
40
/// The tool accepts flexible argument ordering:
38
41
/// - **Issuer DID** (`did:plc:...` or `did:web:...`) - Identity of the expected signature issuer
39
42
/// - **Verification Key** (`did:key:...`) - Public key for signature verification
40
-
/// - **Record File** (file path) - JSON file containing the signed record to verify
43
+
/// - **Record Input** (file path or `--`) - JSON file containing the signed record to verify, or `--` to read from stdin
41
44
/// - **Parameters** (`key=value`) - Repository and collection context for verification
42
45
///
43
46
/// ## Required Parameters
···
67
70
/// collection=community.lexicon.badge.award
68
71
/// ```
69
72
///
73
+
/// ### Verifying from Stdin
74
+
/// ```bash
75
+
/// echo '{"signatures":[...],"text":"Hello!"}' | \
76
+
/// atproto-record-verify \
77
+
/// did:plc:tgudj2fjm77pzkuawquqhsxm \
78
+
/// did:key:z42tv1pb3Dzog28Q1udyieg1YJP3x1Un5vraE1bttXeCDSpW \
79
+
/// -- \
80
+
/// repository=did:plc:4zutorghlchjxzgceklue4la \
81
+
/// collection=app.bsky.feed.post
82
+
/// ```
83
+
///
70
84
/// ### Input Signed Record Example (`signed_post.json`)
71
85
/// ```json
72
86
/// {
···
138
152
println!("AT Protocol Record Verifying Tool");
139
153
println!();
140
154
println!("USAGE:");
141
-
println!(" atproto-record-verify <ISSUER_DID> <KEY> <RECORD_FILE> repository=<REPO> collection=<COLLECTION> [key=value...]");
155
+
println!(" atproto-record-verify <ISSUER_DID> <KEY> <RECORD_INPUT> repository=<REPO> collection=<COLLECTION> [key=value...]");
142
156
println!();
143
157
println!("ARGUMENTS:");
144
-
println!(" <ISSUER_DID> DID of the issuer (e.g., did:plc:...)");
145
-
println!(" <KEY> DID key for verifying (e.g., did:key:z42tv1...)");
146
-
println!(" <RECORD_FILE> Path to JSON file containing the record to verify");
158
+
println!(" <ISSUER_DID> DID of the issuer (e.g., did:plc:...)");
159
+
println!(" <KEY> DID key for verifying (e.g., did:key:z42tv1...)");
160
+
println!(" <RECORD_INPUT> Path to JSON file containing the record to verify, or '--' to read from stdin");
147
161
println!();
148
162
println!("REQUIRED PARAMETERS:");
149
163
println!(" repository=<REPO> Repository DID context");
150
164
println!(" collection=<COLLECTION> Collection name context");
151
165
println!();
152
-
println!("EXAMPLE:");
166
+
println!("EXAMPLES:");
167
+
println!(" # Verify from file:");
153
168
println!(" atproto-record-verify \\");
154
169
println!(" did:plc:tgudj2fjm77pzkuawquqhsxm \\");
155
170
println!(" did:key:z42tv1pb3Dzog28Q1udyieg1YJP3x1Un5vraE1bttXeCDSpW \\");
156
-
println!(" ./record.json \\");
171
+
println!(" ./signed_record.json \\");
157
172
println!(" repository=did:plc:4zutorghlchjxzgceklue4la \\");
158
173
println!(" collection=community.lexicon.badge.award");
174
+
println!();
175
+
println!(" # Verify from stdin:");
176
+
println!(" echo '{{\"signatures\":[...],...}}' | atproto-record-verify \\");
177
+
println!(" did:plc:tgudj2fjm77pzkuawquqhsxm \\");
178
+
println!(" did:key:z42tv1pb3Dzog28Q1udyieg1YJP3x1Un5vraE1bttXeCDSpW \\");
179
+
println!(" -- \\");
180
+
println!(" repository=did:plc:4zutorghlchjxzgceklue4la \\");
181
+
println!(" collection=app.bsky.feed.post");
159
182
return Ok(());
160
183
}
161
184
···
188
211
}
189
212
Ok(_) => return Err(anyhow!("Unsupported DID method: {}", argument)),
190
213
Err(e) => return Err(anyhow!("Failed to parse DID {}: {}", argument, e)),
214
+
}
215
+
} else if argument == "--" {
216
+
// Read record from stdin
217
+
if record.is_none() {
218
+
let mut stdin_content = String::new();
219
+
io::stdin()
220
+
.read_to_string(&mut stdin_content)
221
+
.map_err(|e| anyhow!("Failed to read from stdin: {}", e))?;
222
+
record = Some(
223
+
serde_json::from_str(&stdin_content)
224
+
.map_err(|e| anyhow!("Failed to parse JSON from stdin: {}", e))?,
225
+
);
226
+
} else {
227
+
return Err(anyhow!("Unexpected argument: {}", argument));
191
228
}
192
229
} else {
193
230
// Assume it's a file path to read the record from