Skip to content

Commit b22dc8f

Browse files
committed
wip(rust/signed_doc): add CBOR encode for signed doc
1 parent 487d728 commit b22dc8f

File tree

9 files changed

+182
-97
lines changed

9 files changed

+182
-97
lines changed

rust/signed_doc/examples/mk_signed_doc.rs

Lines changed: 7 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use std::{
1010

1111
use catalyst_signed_doc::{CatalystSignedDocument, Decode, Decoder, KidUri, Metadata};
1212
use clap::Parser;
13-
use coset::{iana::CoapContentFormat, CborSerializable};
13+
use coset::{CborSerializable, Header};
1414
use ed25519_dalek::{ed25519::signature::Signer, pkcs8::DecodePrivateKey};
1515

1616
fn main() {
@@ -55,16 +55,6 @@ enum Cli {
5555
},
5656
}
5757

58-
const CONTENT_ENCODING_KEY: &str = "Content-Encoding";
59-
const UUID_CBOR_TAG: u64 = 37;
60-
61-
fn encode_cbor_uuid(uuid: &uuid::Uuid) -> coset::cbor::Value {
62-
coset::cbor::Value::Tag(
63-
UUID_CBOR_TAG,
64-
coset::cbor::Value::Bytes(uuid.as_bytes().to_vec()).into(),
65-
)
66-
}
67-
6858
impl Cli {
6959
fn exec(self) -> anyhow::Result<()> {
7060
match self {
@@ -76,12 +66,12 @@ impl Cli {
7666
} => {
7767
let doc_schema = load_schema_from_file(&schema)?;
7868
let json_doc = load_json_from_file(&doc)?;
79-
let json_meta = load_json_from_file(&meta)
69+
let json_meta = &load_json_from_file(&meta)
8070
.map_err(|e| anyhow::anyhow!("Failed to load metadata from file: {e}"))?;
8171
println!("{json_meta}");
8272
validate_json(&json_doc, &doc_schema)?;
8373
let compressed_doc = brotli_compress_json(&json_doc)?;
84-
let empty_cose_sign = build_empty_cose_doc(compressed_doc, &json_meta);
74+
let empty_cose_sign = build_empty_cose_doc(compressed_doc, json_meta)?;
8575
store_cose_file(empty_cose_sign, &output)?;
8676
},
8777
Self::Sign { sk, doc, kid } => {
@@ -159,39 +149,12 @@ fn brotli_compress_json(doc: &serde_json::Value) -> anyhow::Result<Vec<u8>> {
159149
Ok(buf)
160150
}
161151

162-
fn build_empty_cose_doc(doc_bytes: Vec<u8>, meta: &Metadata) -> coset::CoseSign {
163-
let mut builder =
164-
coset::HeaderBuilder::new().content_format(CoapContentFormat::from(meta.content_type()));
165-
166-
if let Some(content_encoding) = meta.content_encoding() {
167-
builder = builder.text_value(
168-
CONTENT_ENCODING_KEY.to_string(),
169-
format!("{content_encoding}").into(),
170-
);
171-
}
172-
let mut protected_header = builder.build();
173-
174-
protected_header.rest.push((
175-
coset::Label::Text("type".to_string()),
176-
encode_cbor_uuid(&meta.doc_type()),
177-
));
178-
protected_header.rest.push((
179-
coset::Label::Text("id".to_string()),
180-
encode_cbor_uuid(&meta.doc_id()),
181-
));
182-
protected_header.rest.push((
183-
coset::Label::Text("ver".to_string()),
184-
encode_cbor_uuid(&meta.doc_ver()),
185-
));
186-
let meta_rest = meta.extra().header_rest().unwrap_or_default();
187-
188-
if !meta_rest.is_empty() {
189-
protected_header.rest.extend(meta_rest);
190-
}
191-
coset::CoseSignBuilder::new()
152+
fn build_empty_cose_doc(doc_bytes: Vec<u8>, meta: &Metadata) -> anyhow::Result<coset::CoseSign> {
153+
let protected_header = Header::try_from(meta)?;
154+
Ok(coset::CoseSignBuilder::new()
192155
.protected(protected_header)
193156
.payload(doc_bytes)
194-
.build()
157+
.build())
195158
}
196159

197160
fn load_cose_from_file(cose_path: &PathBuf) -> anyhow::Result<coset::CoseSign> {

rust/signed_doc/src/lib.rs

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ use std::{
1515
use anyhow::anyhow;
1616
pub use builder::Builder;
1717
use content::Content;
18-
use coset::CborSerializable;
19-
pub use metadata::{AdditionalFields, DocumentRef, Metadata, UuidV7};
20-
pub use minicbor::{decode, Decode, Decoder};
18+
use coset::{CborSerializable, Header};
19+
pub use metadata::{AdditionalFields, DocumentRef, Metadata, UuidV4, UuidV7};
20+
pub use minicbor::{decode, encode, Decode, Decoder, Encode};
2121
pub use signature::KidUri;
2222
use signature::Signatures;
2323

@@ -56,19 +56,19 @@ impl CatalystSignedDocument {
5656

5757
/// Return Document Type `UUIDv4`.
5858
#[must_use]
59-
pub fn doc_type(&self) -> uuid::Uuid {
59+
pub fn doc_type(&self) -> UuidV4 {
6060
self.inner.metadata.doc_type()
6161
}
6262

6363
/// Return Document ID `UUIDv7`.
6464
#[must_use]
65-
pub fn doc_id(&self) -> uuid::Uuid {
65+
pub fn doc_id(&self) -> UuidV7 {
6666
self.inner.metadata.doc_id()
6767
}
6868

6969
/// Return Document Version `UUIDv7`.
7070
#[must_use]
71-
pub fn doc_ver(&self) -> uuid::Uuid {
71+
pub fn doc_ver(&self) -> UuidV7 {
7272
self.inner.metadata.doc_ver()
7373
}
7474

@@ -151,3 +151,25 @@ impl Decode<'_, ()> for CatalystSignedDocument {
151151
}
152152
}
153153
}
154+
155+
impl Encode<()> for CatalystSignedDocument {
156+
fn encode<W: minicbor::encode::Write>(
157+
&self, e: &mut encode::Encoder<W>, ctx: &mut (),
158+
) -> Result<(), encode::Error<W::Error>> {
159+
let protected_header = Header::try_from(&self.inner.metadata).map_err(|e| {
160+
minicbor::encode::Error::message(format!("Failed to encode Document Metadata: {e}"))
161+
})?;
162+
let mut builder = coset::CoseSignBuilder::new()
163+
.protected(protected_header)
164+
.payload(self.inner.content.bytes().to_vec());
165+
for signature in self.signatures().signatures() {
166+
builder = builder.add_signature(signature);
167+
}
168+
let cose_sign = builder.build();
169+
let cose_bytes = cose_sign.to_vec().map_err(|e| {
170+
minicbor::encode::Error::message(format!("Failed to encode COSE Sign document: {e}"))
171+
})?;
172+
cose_bytes.encode(e, ctx)?;
173+
Ok(())
174+
}
175+
}

rust/signed_doc/src/metadata/additional_fields.rs

Lines changed: 12 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
use anyhow::anyhow;
44
use coset::{cbor::Value, Label, ProtectedHeader};
55

6-
use super::{cose_protected_header_find, decode_cbor_uuid, encode_cbor_value, DocumentRef, UuidV4};
6+
use super::{cose_protected_header_find, decode_cbor_uuid, encode_cbor_uuid, DocumentRef, UuidV4};
77

88
/// Additional Metadata Fields.
99
///
@@ -36,71 +36,56 @@ impl AdditionalFields {
3636
///
3737
/// # Errors
3838
/// If any internal field cannot be converted into `Value`.
39-
pub fn header_rest(&self) -> anyhow::Result<Vec<(Label, Value)>> {
39+
pub fn header_rest(&self) -> anyhow::Result<Vec<(String, Value)>> {
4040
self.try_into()
4141
}
4242
}
4343

44-
impl TryFrom<&AdditionalFields> for Vec<(Label, Value)> {
44+
impl TryFrom<&AdditionalFields> for Vec<(String, Value)> {
4545
type Error = anyhow::Error;
4646

4747
fn try_from(fields: &AdditionalFields) -> anyhow::Result<Self> {
4848
let mut vec = Vec::new();
4949

5050
if let Some(doc_ref) = &fields.doc_ref {
51-
vec.push((Label::Text("ref".to_string()), doc_ref.try_into()?));
51+
vec.push(("ref".to_string(), Value::try_from(*doc_ref)?));
5252
}
5353

5454
if let Some(template) = &fields.template {
55-
vec.push((Label::Text("template".to_string()), template.try_into()?));
55+
vec.push(("template".to_string(), Value::try_from(*template)?));
5656
}
5757

5858
if let Some(reply) = &fields.reply {
59-
vec.push((Label::Text("reply".to_string()), reply.try_into()?));
59+
vec.push(("reply".to_string(), Value::try_from(*reply)?));
6060
}
6161

6262
if let Some(section) = &fields.section {
63-
vec.push((
64-
Label::Text("section".to_string()),
65-
Value::Text(section.clone()),
66-
));
63+
vec.push(("section".to_string(), Value::Text(section.clone())));
6764
}
6865

6966
if let Some(collabs) = &fields.collabs {
7067
if !collabs.is_empty() {
7168
vec.push((
72-
Label::Text("collabs".to_string()),
69+
"collabs".to_string(),
7370
Value::Array(collabs.iter().cloned().map(Value::Text).collect()),
7471
));
7572
}
7673
}
7774

7875
if let Some(brand_id) = &fields.brand_id {
79-
vec.push((
80-
Label::Text("brand_id".to_string()),
81-
encode_cbor_value(brand_id)?,
82-
));
76+
vec.push(("brand_id".to_string(), encode_cbor_uuid(brand_id)?));
8377
}
8478

8579
if let Some(campaign_id) = &fields.campaign_id {
86-
vec.push((
87-
Label::Text("campaign_id".to_string()),
88-
encode_cbor_value(campaign_id)?,
89-
));
80+
vec.push(("campaign_id".to_string(), encode_cbor_uuid(campaign_id)?));
9081
}
9182

9283
if let Some(election_id) = &fields.election_id {
93-
vec.push((
94-
Label::Text("election_id".to_string()),
95-
encode_cbor_value(election_id)?,
96-
));
84+
vec.push(("election_id".to_string(), encode_cbor_uuid(election_id)?));
9785
}
9886

9987
if let Some(category_id) = &fields.category_id {
100-
vec.push((
101-
Label::Text("category_id".to_string()),
102-
encode_cbor_value(*category_id)?,
103-
));
88+
vec.push(("category_id".to_string(), encode_cbor_uuid(*category_id)?));
10489
}
10590

10691
Ok(vec)

rust/signed_doc/src/metadata/document_id.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
//! Document ID.
22
use std::fmt::{Display, Formatter};
33

4-
use super::UuidV7;
4+
use coset::cbor::Value;
5+
6+
use super::{encode_cbor_uuid, UuidV7};
57

68
/// Catalyst Document ID.
79
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, serde::Deserialize)]
@@ -27,3 +29,17 @@ impl From<UuidV7> for DocumentId {
2729
Self(uuid)
2830
}
2931
}
32+
33+
impl From<DocumentId> for UuidV7 {
34+
fn from(value: DocumentId) -> Self {
35+
value.0
36+
}
37+
}
38+
39+
impl TryFrom<DocumentId> for Value {
40+
type Error = anyhow::Error;
41+
42+
fn try_from(value: DocumentId) -> Result<Self, Self::Error> {
43+
encode_cbor_uuid(value.0).map_err(|e| anyhow::anyhow!("{e}"))
44+
}
45+
}

rust/signed_doc/src/metadata/document_ref.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! Catalyst Signed Document Metadata.
22
use coset::cbor::Value;
33

4-
use super::{decode_cbor_uuid, encode_cbor_value, UuidV7};
4+
use super::{decode_cbor_uuid, encode_cbor_uuid, UuidV7};
55

66
/// Reference to a Document.
77
#[derive(Copy, Clone, Debug, serde::Serialize, serde::Deserialize)]
@@ -21,16 +21,16 @@ pub enum DocumentRef {
2121
},
2222
}
2323

24-
impl TryFrom<&DocumentRef> for Value {
24+
impl TryFrom<DocumentRef> for Value {
2525
type Error = anyhow::Error;
2626

27-
fn try_from(value: &DocumentRef) -> Result<Self, Self::Error> {
27+
fn try_from(value: DocumentRef) -> Result<Self, Self::Error> {
2828
match value {
29-
DocumentRef::Latest { id } => encode_cbor_value(id),
29+
DocumentRef::Latest { id } => encode_cbor_uuid(id),
3030
DocumentRef::WithVer { id, ver } => {
3131
Ok(Value::Array(vec![
32-
encode_cbor_value(id)?,
33-
encode_cbor_value(ver)?,
32+
encode_cbor_uuid(id)?,
33+
encode_cbor_uuid(ver)?,
3434
]))
3535
},
3636
}

rust/signed_doc/src/metadata/document_type.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
//! Document Type.
22
use std::fmt::{Display, Formatter};
33

4-
use super::UuidV4;
4+
use coset::cbor::Value;
5+
6+
use super::{encode_cbor_uuid, UuidV4};
57

68
/// Catalyst Document Type.
79
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, serde::Deserialize)]
@@ -27,3 +29,17 @@ impl From<UuidV4> for DocumentType {
2729
Self(value)
2830
}
2931
}
32+
33+
impl From<DocumentType> for UuidV4 {
34+
fn from(value: DocumentType) -> Self {
35+
value.0
36+
}
37+
}
38+
39+
impl TryFrom<DocumentType> for Value {
40+
type Error = anyhow::Error;
41+
42+
fn try_from(value: DocumentType) -> Result<Self, Self::Error> {
43+
encode_cbor_uuid(value.0).map_err(|e| anyhow::anyhow!("{e}"))
44+
}
45+
}

rust/signed_doc/src/metadata/document_version.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
//! Document Version.
22
use std::fmt::{Display, Formatter};
33

4-
use super::UuidV7;
4+
use coset::cbor::Value;
5+
6+
use super::{encode_cbor_uuid, UuidV7};
57

68
/// Catalyst Document Version.
79
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, serde::Deserialize)]
@@ -26,3 +28,17 @@ impl From<UuidV7> for DocumentVersion {
2628
Self(value)
2729
}
2830
}
31+
32+
impl From<DocumentVersion> for UuidV7 {
33+
fn from(value: DocumentVersion) -> Self {
34+
value.0
35+
}
36+
}
37+
38+
impl TryFrom<DocumentVersion> for Value {
39+
type Error = anyhow::Error;
40+
41+
fn try_from(value: DocumentVersion) -> Result<Self, Self::Error> {
42+
encode_cbor_uuid(value.0).map_err(|e| anyhow::anyhow!("{e}"))
43+
}
44+
}

0 commit comments

Comments
 (0)