Skip to content

Commit 9edb8bc

Browse files
authored
feat(rust/signed-doc): mk_signed_doc cli tool to accept bip32 extended private keys (#279)
* modify signature generation * fix spelling * fix clippy
1 parent 1ae00a2 commit 9edb8bc

File tree

6 files changed

+24
-19
lines changed

6 files changed

+24
-19
lines changed

rust/signed_doc/Cargo.toml

+2
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ clap = { version = "4.5.23", features = ["derive", "env"] }
2626
jsonschema = "0.28.3"
2727
jsonpath-rust = "0.7.5"
2828
futures = "0.3.31"
29+
ed25519-bip32 = "0.4.1" # used by the `mk_signed_doc` cli tool
30+
2931

3032
[dev-dependencies]
3133
base64-url = "3.0.0"

rust/signed_doc/bins/mk_signed_doc.rs

+10-10
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,14 @@
33
#![allow(missing_docs, clippy::missing_docs_in_private_items)]
44

55
use std::{
6-
fs::{read_to_string, File},
6+
fs::File,
77
io::{Read, Write},
88
path::PathBuf,
99
};
1010

1111
use anyhow::Context;
1212
use catalyst_signed_doc::{Builder, CatalystSignedDocument, IdUri};
1313
use clap::Parser;
14-
use ed25519_dalek::pkcs8::DecodePrivateKey;
1514

1615
fn main() {
1716
if let Err(err) = Cli::parse().exec() {
@@ -37,8 +36,8 @@ enum Cli {
3736
/// Path to the formed (could be empty, without any signatures) COSE document
3837
/// This exact file would be modified and new signature would be added
3938
doc: PathBuf,
40-
/// Path to the secret key in PEM format
41-
sk: PathBuf,
39+
/// Bip32 extended secret key hex bytes (includes `chain_code`)
40+
sk_hex: String,
4241
/// Signer kid
4342
kid: IdUri,
4443
},
@@ -77,13 +76,14 @@ impl Cli {
7776
);
7877
save_signed_doc(signed_doc, &output)?;
7978
},
80-
Self::Sign { sk, doc, kid } => {
81-
let sk = load_secret_key_from_file(&sk).context("Failed to load SK FILE")?;
79+
Self::Sign { doc, sk_hex, kid } => {
80+
let sk = load_secret_key(&sk_hex)?;
8281
let cose_bytes = read_bytes_from_file(&doc)?;
8382
let signed_doc = signed_doc_from_bytes(cose_bytes.as_slice())?;
83+
8484
let new_signed_doc = signed_doc
8585
.into_builder()
86-
.add_signature(sk.to_bytes(), kid)?
86+
.add_signature(|message| sk.sign::<()>(&message).to_bytes().to_vec(), kid)?
8787
.build();
8888
save_signed_doc(new_signed_doc, &doc)?;
8989
},
@@ -144,8 +144,8 @@ fn write_bytes_to_file(bytes: &[u8], output: &PathBuf) -> anyhow::Result<()> {
144144
.context(format!("Failed to write to file {output:?}"))
145145
}
146146

147-
fn load_secret_key_from_file(sk_path: &PathBuf) -> anyhow::Result<ed25519_dalek::SigningKey> {
148-
let sk_str = read_to_string(sk_path)?;
149-
let sk = ed25519_dalek::SigningKey::from_pkcs8_pem(&sk_str)?;
147+
fn load_secret_key(sk_hex: &str) -> anyhow::Result<ed25519_bip32::XPrv> {
148+
let sk_bytes = hex::decode(sk_hex)?;
149+
let sk = ed25519_bip32::XPrv::from_slice_verified(&sk_bytes)?;
150150
Ok(sk)
151151
}

rust/signed_doc/src/builder.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
//! Catalyst Signed Document Builder.
22
use catalyst_types::{id_uri::IdUri, problem_report::ProblemReport};
3-
use ed25519_dalek::{ed25519::signature::Signer, SecretKey};
43

54
use crate::{
65
CatalystSignedDocument, Content, InnerCatalystSignedDocument, Metadata, Signatures,
@@ -55,20 +54,21 @@ impl Builder {
5554
/// Fails if a `CatalystSignedDocument` cannot be created due to missing metadata or
5655
/// content, due to malformed data, or when the signed document cannot be
5756
/// converted into `coset::CoseSign`.
58-
pub fn add_signature(mut self, sk: SecretKey, kid: IdUri) -> anyhow::Result<Self> {
57+
pub fn add_signature(
58+
mut self, sign_fn: impl FnOnce(Vec<u8>) -> Vec<u8>, kid: IdUri,
59+
) -> anyhow::Result<Self> {
5960
let cose_sign = self
6061
.0
6162
.as_cose_sign()
6263
.map_err(|e| anyhow::anyhow!("Failed to sign: {e}"))?;
6364

64-
let sk = ed25519_dalek::SigningKey::from_bytes(&sk);
6565
let protected_header = coset::HeaderBuilder::new().key_id(kid.to_string().into_bytes());
6666

6767
let mut signature = coset::CoseSignatureBuilder::new()
6868
.protected(protected_header.build())
6969
.build();
7070
let data_to_sign = cose_sign.tbs_data(&[], &signature);
71-
signature.signature = sk.sign(&data_to_sign).to_vec();
71+
signature.signature = sign_fn(data_to_sign);
7272
self.0.signatures.push(kid, signature);
7373

7474
Ok(self)

rust/signed_doc/src/validator/rules/signature_kid.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ mod tests {
4444
id_uri::IdUri,
4545
uuid::{UuidV4, UuidV7},
4646
};
47+
use ed25519_dalek::ed25519::signature::Signer;
4748

4849
use super::*;
4950
use crate::{Builder, ContentType};
@@ -67,7 +68,7 @@ mod tests {
6768
"content-type": ContentType::Json.to_string(),
6869
}))
6970
.unwrap()
70-
.add_signature(sk.to_bytes(), kid)
71+
.add_signature(|m| sk.sign(&m).to_vec(), kid)
7172
.unwrap()
7273
.build();
7374

rust/signed_doc/tests/common/mod.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use std::str::FromStr;
44

55
use catalyst_signed_doc::*;
66
use catalyst_types::id_uri::role_index::RoleIndex;
7+
use ed25519_dalek::ed25519::signature::Signer;
78

89
pub fn test_metadata() -> (UuidV7, UuidV4, serde_json::Value) {
910
let uuid_v7 = UuidV7::new();
@@ -84,7 +85,7 @@ pub fn create_dummy_signed_doc(
8485
let signed_doc = Builder::new()
8586
.with_decoded_content(content)
8687
.with_json_metadata(with_metadata.unwrap_or(metadata))?
87-
.add_signature(sk.to_bytes(), kid.clone())?
88+
.add_signature(|m| sk.sign(&m).to_vec(), kid.clone())?
8889
.build();
8990

9091
Ok((signed_doc, pk, kid))

rust/signed_doc/tests/signature.rs

+4-3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
33
use catalyst_signed_doc::{providers::tests::TestVerifyingKeyProvider, *};
44
use catalyst_types::id_uri::role_index::RoleIndex;
5+
use ed25519_dalek::ed25519::signature::Signer;
56

67
mod common;
78

@@ -36,11 +37,11 @@ async fn multiple_signatures_validation_test() {
3637
.with_decoded_content(serde_json::to_vec(&serde_json::Value::Null).unwrap())
3738
.with_json_metadata(common::test_metadata().2)
3839
.unwrap()
39-
.add_signature(sk1.to_bytes(), kid1.clone())
40+
.add_signature(|m| sk1.sign(&m).to_vec(), kid1.clone())
4041
.unwrap()
41-
.add_signature(sk2.to_bytes(), kid2.clone())
42+
.add_signature(|m| sk2.sign(&m).to_vec(), kid2.clone())
4243
.unwrap()
43-
.add_signature(sk3.to_bytes(), kid3.clone())
44+
.add_signature(|m| sk3.sign(&m).to_vec(), kid3.clone())
4445
.unwrap()
4546
.build();
4647

0 commit comments

Comments
 (0)