Comment on page
DNS Witness Flow Schema
This example shows how to create a Rust application with rebase that uses TreeLDR to define a simple DNS witness flow VC. The program will consist of:
- a TreeLDR schema defining the credential type,
- an implementation of Rebase's
SchemaType
trait for the credential schema.
First, create a new Rust project using cargo:
cargo new dns-example
cd dns-example
The
src
directory will contain the sources of our application. For now, it only contains a main.rs
file with a dummy main function definition.Create a new
src/dns.tldr
file that will contain the VC definition:base <https://example.com/>;
use <http://www.w3.org/2000/01/rdf-schema#> as rdfs;
use <http://www.w3.org/2001/XMLSchema#> as xs;
use <https://treeldr.org/> as tldr;
use <https://www.w3.org/2018/credentials#> as vc;
/// DNS Verification Message.
type DnsVerificationMessage {
// Include the type inside the layout as `@type`.
// This will force TreeLDR to generate a type scoped context
// for `DnsVerificationMessage` when generating the
// JSON-LD context.
rdf:type as @type: required multiple &rdfs:Class,
timestamp: required xs:dateTime,
dnsServer: required xs:string
}
/// DNS Verification Subject.
layout DnsVerificationSubject for rdfs:Resource {
tldr:self as id: required &rdfs:Resource,
schema:sameAs: &rdfs:Resource
}
/// DNS Verification VC.
type DnsVerification =
vc:VerifiableCredential &
all vc:credentialSubject: (rdfs:Resource with DnsVerificationSubject) &
all vc:evidence: DnsVerificationMessage;
The
DnsVerification
type is a vc:VerifiableCredential
where the credential subject is any resource represented with the DnsVerificationSubject
layout, and evidences are any DnsVerificationMessage
.Instead of defining the
vc:VerifiableCredential
type ourselves in this file, we will rely on an example file provided in the TreeLDR repository that contains VC type definitions. In the terminal, use the following command to download this file as src/vc.tldr
:curl https://raw.githubusercontent.com/spruceid/treeldr/main/examples/vc.tldr > src/vc.tldr
We will embed the previous schema definition into the Rust program as a type definition using the
#[tldr]
procedural macro attribute provided by the treeldr-rust-macros
crate. This crate rely on the treeldr-rust-prelude
crate. First, add those two crates to the dependencies of the Rust program by adding the following lines to the Cargo.toml
file under the [dependencies]
section:treeldr-rust-macros = { git = "https://github.com/spruceid/treeldr.git" }
treeldr-rust-prelude = { git = "https://github.com/spruceid/treeldr.git" }
Then add the following module definition annotated with the
#[tldr]
procedural macro attribute to the src/main.rs
file:#[tldr(
"src/dns.tldr"
)]
mod schema {
#[prefix("http://www.w3.org/2001/XMLSchema#")]
pub mod xs {}
#[prefix("https://www.w3.org/2018/credentials#")]
pub mod vc {}
#[prefix("https://example.com/")]
pub mod example {}
}
At compile time, this macro call will expand to add the following two type definitions in the
schema::example
module:pub struct DnsVerificationMessage {
type_: BTreeSet<treeldr_rust_prelude::Id>,
timestamp: super::xs::DateTime,
dns_server: super::xs::String
}
pub struct DnsVerificationSubject {
id: treeldr_rust_prelude::Id,
same_as: Option<treeldr_rust_prelude::Id>
}
Contrarily to the "Basic Post" example,
SchemaType
will not directly be implemented for the credential subject type (here DnsVerificationMessage
). Instead, we create a dedicated Schema
type that will generate the credential subjects and evidence. Add the following items in the src/main.rs
file:use ssi::{one_or_many::OneOrMany, vc::Evidence};
use rebase::schema::schema_type::{SchemaError, SchemaType};
use rebase::signer::signer::DID as SignerDID;
pub struct Schema {
pub domain: String,
pub key_type: SignerDID,
}
impl SchemaType for Schema {
fn context(&self) -> Result<serde_json::Value, SchemaError> {
todo!()
}
fn subject(&self) -> Result<serde_json::Value, SchemaError> {
todo!()
}
fn evidence(&self) -> Result<Option<OneOrMany<Evidence>>, SchemaError> {
todo!()
}
fn types(&self) -> Result<Vec<String>, SchemaError> {
Ok(vec![
"VerifiableCredential".to_string(),
"DnsVerification".to_string(),
])
}
}
We now need to fill-in the blanks for the
context
, subject
and evidence
methods.We can then generate the associated JSON-LD context using the following command:
tldrc -i src/vc.tldr -i src/dns.tldr json-ld-context -c https://www.w3.org/2018/credentials/v1 https://example.com/DnsVerificationMessage
Using the output of the command, replace the
todo!()
in the context
method with:Ok(serde_json::json!([
"https://www.w3.org/2018/credentials/v1",
{
"sameAs": "http://schema.org/sameAs",
"DnsVerification": "https://example.com/DnsVerification",
"DnsVerificationMessage": {
"@id": "https://example.com/DnsVerificationMessage",
"@context": {
"timestamp": {
"@id": "https://example.com/timestamp",
"@type": "http://www.w3.org/2001/XMLSchema#dateTime"
},
"dnsServer": "https://example.com/dnsServer"
}
}
}
]))
The subject of the VC, a
DnsVerifiactionSubject
, is derived from the key type. We use schema::example::DnsVerificationSubject
to build the subject and convert it into a serde_json::Value
. Replace the todo!()
in the subject
method with:use treeldr_rust_prelude::{Id, IntoJsonLd};
let signer_type = SignerTypes::new(&self.key_type)?;
let signer_did = signer_type
.did_id()
.map_err(|e| SchemaError::BadSubject(e.to_string()))?;
let mut subject = schema::example::DnsVerificationSubject::new(Id::try_from(signer_did).unwrap());
subject.same_as.insert(Id::try_from(format!("dns:{}", self.domain)).unwrap());
Ok(subject.into_json_ld().into())
The evidence is a
DnsVerificationMessage
, however we will not be able to use the schema::example::DnsVerificationMessage
type defined by TreeLDR since rebase expect an ssi::vc::Evidence
instance. Instead we will closely follow the structure of a DnsVerificationMessage
to build the evidence by hand. Replace the todo!()
in the evidence
method with:let mut evidence_map = HashMap::new();
evidence_map.insert(
"timestamp".to_string(),
serde_json::Value::String(Utc::now().to_rfc3339_opts(SecondsFormat::Millis, true)),
);
evidence_map.insert(
"dnsServer".to_string(),
serde_json::Value::String("https://cloudflare-dns.com/dns-query".to_string()),
);
let evidence = Evidence {
id: None,
type_: vec!["DnsVerificationMessage".to_string()],
property_set: Some(evidence_map),
};
Ok(Some(OneOrMany::One(evidence)))
Last modified 1yr ago