Skip to content

Commit

Permalink
feat(dataverse): support unverified credentials (tx signatures as proof)
Browse files Browse the repository at this point in the history
  • Loading branch information
ccamel committed Dec 10, 2024
1 parent 47133a2 commit 83c0305
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 3 deletions.
77 changes: 76 additions & 1 deletion contracts/axone-dataverse/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ pub fn execute(

pub mod execute {
use super::*;
use crate::credential::error::VerificationError;
use crate::credential::vc::VerifiableCredential;
use crate::registrar::credential::DataverseCredential;
use crate::registrar::registry::ClaimRegistrar;
Expand All @@ -101,7 +102,15 @@ pub mod execute {
let rdf_quads = reader.read_all()?;
let vc_dataset = Dataset::from(rdf_quads.as_slice());
let vc = VerifiableCredential::try_from(&vc_dataset)?;
vc.verify(&deps)?;

// check proofs if any.
// accept unverified credentials if the issuer matches the sender, as the transaction's
// signature serves as proof.
if !vc.proof.is_empty() {
vc.verify(&deps)?;
} else if !vc.is_issued_by(&info.sender) {
Err(VerificationError::NoSuitableProof)?;
}

let credential = DataverseCredential::try_from((env, info, &vc))?;
let registrar = ClaimRegistrar::try_new(deps.storage)?;
Expand Down Expand Up @@ -444,6 +453,72 @@ _:b0 <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://example.org/exam
))
}

#[test]
fn submit_unverified_claims_matching_sender() {
let mut deps = mock_dependencies();
deps.querier.update_wasm(|query| match query {
WasmQuery::Smart { contract_addr, msg } => {
if contract_addr != "my-dataverse-addr" {
return SystemResult::Err(SystemError::NoSuchContract {
addr: contract_addr.to_string(),
});
}
let query_msg: StdResult<axone_cognitarium::msg::QueryMsg> = from_json(msg);
assert_eq!(
query_msg,
Ok(axone_cognitarium::msg::QueryMsg::Select {
query: SelectQuery {
prefixes: vec![],
limit: Some(1u32),
select: vec![SelectItem::Variable("p".to_string())],
r#where: WhereClause::Bgp {
patterns: vec![TriplePattern {
subject: VarOrNode::Node(Node::NamedNode(IRI::Full(
"http://example.edu/credentials/3732".to_string(),
))),
predicate: VarOrNamedNode::Variable("p".to_string()),
object: VarOrNodeOrLiteral::Variable("o".to_string()),
}]
},
}
})
);

let select_resp = SelectResponse {
results: Results { bindings: vec![] },
head: Head { vars: vec![] },
};
SystemResult::Ok(ContractResult::Ok(to_json_binary(&select_resp).unwrap()))
}
_ => SystemResult::Err(SystemError::Unknown {}),
});

DATAVERSE
.save(
deps.as_mut().storage,
&Dataverse {
name: "my-dataverse".to_string(),
triplestore_address: Addr::unchecked("my-dataverse-addr"),
},
)
.unwrap();

let resp = execute(
deps.as_mut(),
mock_env(),
message_info(
&Addr::unchecked("axone178mjppxcf3n9q3q7utdwrajdal0tsqvymz0900"),
&[],
),
ExecuteMsg::SubmitClaims {
claims: Binary::new(read_test_data("vc-eddsa-2020-ok-unsecured-trusted.nq")),
format: Some(RdfDatasetFormat::NQuads),
},
);

assert!(resp.is_ok());
}

#[test]
fn submit_unverified_claims() {
let resp = execute(
Expand Down
45 changes: 44 additions & 1 deletion contracts/axone-dataverse/src/credential/vc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@ use crate::credential::proof::{Proof, ProofPurpose};
use crate::credential::rdf_marker::*;
use axone_rdf::dataset::QuadIterator;
use axone_rdf::dataset::{Dataset, QuadPattern};
use cosmwasm_std::DepsMut;
use bech32::Bech32;
use cosmwasm_std::{Addr, DepsMut};
use itertools::Itertools;
use rio_api::model::{BlankNode, Literal, NamedNode, Subject, Term};
use ripemd::Ripemd160;
use sha2::Digest;

#[derive(Debug, PartialEq)]
pub struct VerifiableCredential<'a> {
Expand Down Expand Up @@ -87,6 +90,46 @@ impl<'a> VerifiableCredential<'a> {
)
}

// Tells if the credential was issued by the given address.
pub fn is_issued_by(&self, addr: &Addr) -> bool {
const SECP256K1PUB_MULTICODEC_PREFIX: [u8; 2] = [0xe7, 0x01];
const ED25519PUB_MULTICODEC_PREFIX: [u8; 2] = [0xed, 0x01];
const PREFIX: &str = "did:key:";

if !self.issuer.starts_with(PREFIX) {
return false;
}

let encoded = &self.issuer[PREFIX.len()..];
let decoded = match multibase::decode(encoded) {
Ok((_, bytes)) => bytes,
Err(_) => return false,
};

let (prefix, pubkey) = decoded.split_at(2);
if prefix != SECP256K1PUB_MULTICODEC_PREFIX && prefix != ED25519PUB_MULTICODEC_PREFIX {
return false;
}

let (hrp, _) = match bech32::decode(addr.as_str()) {
Ok(decoded) => decoded,
Err(_) => return false,
};

let pubkey_hash = {
let hash = sha2::Sha256::digest(pubkey);

Ripemd160::digest(hash)
};

let bech32_addr = match bech32::encode::<Bech32>(hrp, &pubkey_hash) {
Ok(addr) => addr,
Err(_) => return false,
};

bech32_addr == addr.as_str()
}

fn extract_identifier(
dataset: &'a Dataset<'a>,
) -> Result<NamedNode<'a>, InvalidCredentialError> {
Expand Down
4 changes: 3 additions & 1 deletion contracts/axone-dataverse/src/msg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ pub enum ExecuteMsg {
///
/// 2. **Unique Identifier Mandate**: Each Verifiable Credential within the dataverse must possess a unique identifier.
///
/// 3. **Issuer Signature**: Claims must bear the issuer's signature. This signature must be verifiable, ensuring authenticity and credibility.
/// 3. **Issuer Verification**: Claims are accepted if they either:
/// - Bear a verifiable issuer's signature to ensure authenticity.
/// - Originate from the transaction sender, in which case the transaction signature serves as proof of authenticity.
///
/// 4. **Content**: The actual implementation supports the submission of a single Verifiable Credential, containing a single claim.
///
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<did:key:zDnaeUm3QkcyZWZTPttxB711jgqRDhkwvhF485SFw1bDZ9AQw> <https://example.org/examples#degree> _:b2 .
<http://example.edu/credentials/3732> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://example.org/examples#UniversityDegreeCredential> .
<http://example.edu/credentials/3732> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://www.w3.org/2018/credentials#VerifiableCredential> .
<http://example.edu/credentials/3732> <https://www.w3.org/2018/credentials#credentialSubject> <did:key:zDnaeUm3QkcyZWZTPttxB711jgqRDhkwvhF485SFw1bDZ9AQw> .
<http://example.edu/credentials/3732> <https://www.w3.org/2018/credentials#expirationDate> "2026-02-16T00:00:00Z"^^<http://www.w3.org/2001/XMLSchema#dateTime> .
<http://example.edu/credentials/3732> <https://www.w3.org/2018/credentials#issuanceDate> "2024-02-16T00:00:00Z"^^<http://www.w3.org/2001/XMLSchema#dateTime> .
<http://example.edu/credentials/3732> <https://www.w3.org/2018/credentials#issuer> <did:key:zQ3shsoarhrw7SoyUyoCbwv8k2BRRLeqkjRkffGqx7WNpMQgw> .
_:b2 <http://schema.org/name> "Bachelor of Science and Arts"^^<http://www.w3.org/1999/02/22-rdf-syntax-ns#HTML> .
_:b2 <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://example.org/examples#BachelorDegree> .

0 comments on commit 83c0305

Please sign in to comment.