From 46cd40c383f72ff27dbdc4a8dec768aa04cd7b9c Mon Sep 17 00:00:00 2001 From: Arnaud Mimart <33665250+amimart@users.noreply.github.com> Date: Fri, 9 Feb 2024 18:10:54 +0100 Subject: [PATCH] feat(dataverse): add ecdsa secp 2019 vc proof support --- .../okp4-dataverse/src/credential/proof.rs | 121 +++++++++++++++--- .../src/credential/rdf_marker.rs | 3 + 2 files changed, 109 insertions(+), 15 deletions(-) diff --git a/contracts/okp4-dataverse/src/credential/proof.rs b/contracts/okp4-dataverse/src/credential/proof.rs index 11cc9019..aa9ea9d3 100644 --- a/contracts/okp4-dataverse/src/credential/proof.rs +++ b/contracts/okp4-dataverse/src/credential/proof.rs @@ -1,17 +1,18 @@ use crate::credential::crypto::{CanonicalizationAlg, CryptoSuite, DigestAlg, SignatureAlg}; use crate::credential::error::InvalidProofError; use crate::credential::rdf_marker::{ - PROOF_RDF_CRYPTOSUITE, PROOF_RDF_PROOF_PURPOSE, PROOF_RDF_PROOF_VALUE, + PROOF_RDF_CRYPTOSUITE, PROOF_RDF_JWS, PROOF_RDF_PROOF_PURPOSE, PROOF_RDF_PROOF_VALUE, PROOF_RDF_PROOF_VALUE_TYPE, PROOF_RDF_VERIFICATION_METHOD, RDF_CREATED, RDF_DATE_TYPE, RDF_TYPE, }; use itertools::Itertools; use okp4_rdf::dataset::{Dataset, QuadIterator}; -use rio_api::model::{GraphName, Literal, Quad, Term}; +use rio_api::model::{GraphName, Literal, NamedNode, Quad, Term}; #[derive(Debug, PartialEq)] pub enum Proof<'a> { Ed25519Signature2020(Ed25519Signature2020Proof<'a>), + EcdsaSecp256k1Signature2019(EcdsaSecp256k1Signature2019Proof<'a>), DataIntegrity(DataIntegrityProof<'a>), } @@ -21,6 +22,9 @@ impl<'a> Proof<'a> { Self::Ed25519Signature2020(proof) => { (proof.verification_method.controller, proof.purpose) } + Self::EcdsaSecp256k1Signature2019(proof) => { + (proof.verification_method.controller, proof.purpose) + } Proof::DataIntegrity(proof) => (proof.verification_method.controller, proof.purpose), }; @@ -34,6 +38,11 @@ impl<'a> Proof<'a> { DigestAlg::Sha256, SignatureAlg::Ed25519, ), + Proof::EcdsaSecp256k1Signature2019(_) => ( + CanonicalizationAlg::Urdna2015, + DigestAlg::Sha256, + SignatureAlg::Secp256k1, + ), Proof::DataIntegrity(p) => ( CanonicalizationAlg::Urdna2015, DigestAlg::Sha256, @@ -46,6 +55,7 @@ impl<'a> Proof<'a> { pub fn pub_key(&'a self) -> &'a [u8] { match self { Proof::Ed25519Signature2020(p) => &p.verification_method.pub_key, + Proof::EcdsaSecp256k1Signature2019(p) => &p.verification_method.pub_key, Proof::DataIntegrity(p) => &p.verification_method.pub_key, } } @@ -53,6 +63,7 @@ impl<'a> Proof<'a> { pub fn signature(&'a self) -> &'a [u8] { match self { Proof::Ed25519Signature2020(p) => &p.value, + Proof::EcdsaSecp256k1Signature2019(p) => p.jws.as_bytes(), Proof::DataIntegrity(p) => &p.value, } } @@ -60,6 +71,7 @@ impl<'a> Proof<'a> { pub fn options(&'a self) -> &'a [Quad<'a>] { match self { Proof::Ed25519Signature2020(p) => p.options.as_ref(), + Proof::EcdsaSecp256k1Signature2019(p) => p.options.as_ref(), Proof::DataIntegrity(p) => p.options.as_ref(), } } @@ -187,11 +199,37 @@ impl<'a> Proof<'a> { }) } - fn extract_proof_options(dataset: &'a Dataset<'a>, proof_graph: GraphName<'a>) -> Dataset<'a> { + fn extract_jws( + dataset: &'a Dataset<'a>, + proof_graph: GraphName<'a>, + ) -> Result<&'a str, InvalidProofError> { + dataset + .match_pattern(None, Some(PROOF_RDF_JWS), None, Some(Some(proof_graph))) + .objects() + .exactly_one() + .map_err(|e| match e.size_hint() { + (_, Some(_)) => InvalidProofError::Malformed( + "Proof cannot have more than one proof jws".to_string(), + ), + _ => InvalidProofError::MissingProofValue, + }) + .and_then(|o| match o { + Term::Literal(Literal::Simple { value }) => Ok(value), + _ => Err(InvalidProofError::Malformed( + "Proof jws must be a string".to_string(), + )), + }) + } + + fn extract_proof_options( + dataset: &'a Dataset<'a>, + proof_graph: GraphName<'a>, + value_predicate: NamedNode<'a>, + ) -> Dataset<'a> { Dataset::new( dataset .match_pattern(None, None, None, Some(Some(proof_graph))) - .skip_pattern((None, Some(PROOF_RDF_PROOF_VALUE), None, None).into()) + .skip_pattern((None, Some(value_predicate), None, None).into()) .map(|quad| Quad { subject: quad.subject, predicate: quad.predicate, @@ -230,6 +268,11 @@ impl<'a> TryFrom<(&'a Dataset<'a>, GraphName<'a>)> for Proof<'a> { "https://w3id.org/security#Ed25519Signature2020" => Ok(Self::Ed25519Signature2020( Ed25519Signature2020Proof::try_from((dataset, proof_graph))?, )), + "https://w3id.org/security#EcdsaSecp256k1Signature2019" => { + Ok(Self::EcdsaSecp256k1Signature2019( + EcdsaSecp256k1Signature2019Proof::try_from((dataset, proof_graph))?, + )) + } "https://w3id.org/security#DataIntegrityProof" => Ok(Self::DataIntegrity( DataIntegrityProof::try_from((dataset, proof_graph))?, )), @@ -238,15 +281,6 @@ impl<'a> TryFrom<(&'a Dataset<'a>, GraphName<'a>)> for Proof<'a> { } } -#[derive(Debug, PartialEq)] -pub struct Ed25519Signature2020Proof<'a> { - verification_method: Ed25519VerificationKey2020<'a>, - created: &'a str, - purpose: ProofPurpose, - value: Vec, - options: Dataset<'a>, -} - #[derive(Debug, PartialEq, Copy, Clone)] pub enum ProofPurpose { AssertionMethod, @@ -262,6 +296,15 @@ impl<'a> From<&'a str> for ProofPurpose { } } +#[derive(Debug, PartialEq)] +pub struct Ed25519Signature2020Proof<'a> { + verification_method: Ed25519VerificationKey2020<'a>, + created: &'a str, + purpose: ProofPurpose, + value: Vec, + options: Dataset<'a>, +} + impl<'a> TryFrom<(&'a Dataset<'a>, GraphName<'a>)> for Ed25519Signature2020Proof<'a> { type Error = InvalidProofError; @@ -278,7 +321,7 @@ impl<'a> TryFrom<(&'a Dataset<'a>, GraphName<'a>)> for Ed25519Signature2020Proof created: Proof::extract_created(dataset, proof_graph)?, purpose: p_purpose.into(), value: p_value, - options: Proof::extract_proof_options(dataset, proof_graph), + options: Proof::extract_proof_options(dataset, proof_graph, PROOF_RDF_PROOF_VALUE), }) } } @@ -303,6 +346,54 @@ impl<'a> TryFrom<&'a str> for Ed25519VerificationKey2020<'a> { } } +#[derive(Debug, PartialEq)] +pub struct EcdsaSecp256k1Signature2019Proof<'a> { + verification_method: EcdsaSecp256k1VerificationKey2019<'a>, + created: &'a str, + purpose: ProofPurpose, + jws: &'a str, + options: Dataset<'a>, +} + +impl<'a> TryFrom<(&'a Dataset<'a>, GraphName<'a>)> for EcdsaSecp256k1Signature2019Proof<'a> { + type Error = InvalidProofError; + + fn try_from( + (dataset, proof_graph): (&'a Dataset<'a>, GraphName<'a>), + ) -> Result { + let v_method = Proof::extract_verification_method(dataset, proof_graph)?; + let p_purpose = Proof::extract_proof_purpose(dataset, proof_graph)?; + + Ok(Self { + verification_method: v_method.try_into()?, + created: Proof::extract_created(dataset, proof_graph)?, + purpose: p_purpose.into(), + jws: Proof::extract_jws(dataset, proof_graph)?, + options: Proof::extract_proof_options(dataset, proof_graph, PROOF_RDF_JWS), + }) + } +} + +#[derive(Debug, PartialEq)] +pub struct EcdsaSecp256k1VerificationKey2019<'a> { + id: &'a str, + controller: &'a str, + pub_key: Vec, +} + +impl<'a> TryFrom<&'a str> for EcdsaSecp256k1VerificationKey2019<'a> { + type Error = InvalidProofError; + + fn try_from(value: &'a str) -> Result { + let (controller, key) = Proof::parse_verification_method(value)?; + Ok(Self { + id: value, + controller, + pub_key: multiformats::decode_secp256k1_key(key)?, + }) + } +} + #[derive(Debug, PartialEq)] pub struct DataIntegrityProof<'a> { cryptosuite: DataIntegrityCryptoSuite, @@ -382,7 +473,7 @@ impl<'a> TryFrom<(&'a Dataset<'a>, GraphName<'a>)> for DataIntegrityProof<'a> { created: Proof::extract_created(dataset, proof_graph)?, purpose: p_purpose.into(), value: p_value, - options: Proof::extract_proof_options(dataset, proof_graph), + options: Proof::extract_proof_options(dataset, proof_graph, PROOF_RDF_PROOF_VALUE), }) } } diff --git a/contracts/okp4-dataverse/src/credential/rdf_marker.rs b/contracts/okp4-dataverse/src/credential/rdf_marker.rs index 346574e0..8b7e7d17 100644 --- a/contracts/okp4-dataverse/src/credential/rdf_marker.rs +++ b/contracts/okp4-dataverse/src/credential/rdf_marker.rs @@ -41,6 +41,9 @@ pub const PROOF_RDF_PROOF_PURPOSE: NamedNode<'_> = NamedNode { pub const PROOF_RDF_PROOF_VALUE: NamedNode<'_> = NamedNode { iri: "https://w3id.org/security#proofValue", }; +pub const PROOF_RDF_JWS: NamedNode<'_> = NamedNode { + iri: "https://w3id.org/security#jws", +}; pub const PROOF_RDF_PROOF_VALUE_TYPE: NamedNode<'_> = NamedNode { iri: "https://w3id.org/security#multibase", };