//! Generated by `trust-tasks-codegen` — do not edit by hand.
//!
//! Spec slug: `keys/sign`. Version: `0.1`.
#[allow(unused_imports)]
use serde::{Deserialize, Serialize};
/// Error types.
pub mod error {
/// Error from a `TryFrom` or `FromStr` implementation.
pub struct ConversionError(::std::borrow::Cow<'static, str>);
impl ::std::error::Error for ConversionError {}
impl ::std::fmt::Display for ConversionError {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> Result<(), ::std::fmt::Error> {
::std::fmt::Display::fmt(&self.0, f)
}
}
impl ::std::fmt::Debug for ConversionError {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> Result<(), ::std::fmt::Error> {
::std::fmt::Debug::fmt(&self.0, f)
}
}
impl From<&'static str> for ConversionError {
fn from(value: &'static str) -> Self {
Self(value.into())
}
}
impl From<String> for ConversionError {
fn from(value: String) -> Self {
Self(value.into())
}
}
}
///Vendor-namespaced extension object per SPEC.md §4.5.1. Each immediate key MUST be a reverse-DNS namespace; structure under each namespace is opaque to the framework.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "title": "Ext",
/// "description": "Vendor-namespaced extension object per SPEC.md §4.5.1. Each immediate key MUST be a reverse-DNS namespace; structure under each namespace is opaque to the framework.",
/// "type": "object",
/// "minProperties": 1,
/// "additionalProperties": true,
/// "propertyNames": {
/// "pattern": "^[a-z][a-z0-9-]*(\\.[a-z0-9-]+)+$"
/// }
///}
/// ```
/// </details>
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)]
#[serde(transparent)]
pub struct Ext(pub ::std::collections::HashMap<ExtKey, ::serde_json::Value>);
impl ::std::ops::Deref for Ext {
type Target = ::std::collections::HashMap<ExtKey, ::serde_json::Value>;
fn deref(&self) -> &::std::collections::HashMap<ExtKey, ::serde_json::Value> {
&self.0
}
}
impl ::std::convert::From<Ext> for ::std::collections::HashMap<ExtKey, ::serde_json::Value> {
fn from(value: Ext) -> Self {
value.0
}
}
impl ::std::convert::From<::std::collections::HashMap<ExtKey, ::serde_json::Value>> for Ext {
fn from(value: ::std::collections::HashMap<ExtKey, ::serde_json::Value>) -> Self {
Self(value)
}
}
///`ExtKey`
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "type": "string",
/// "pattern": "^[a-z][a-z0-9-]*(\\.[a-z0-9-]+)+$"
///}
/// ```
/// </details>
#[derive(::serde::Serialize, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[serde(transparent)]
pub struct ExtKey(::std::string::String);
impl ::std::ops::Deref for ExtKey {
type Target = ::std::string::String;
fn deref(&self) -> &::std::string::String {
&self.0
}
}
impl ::std::convert::From<ExtKey> for ::std::string::String {
fn from(value: ExtKey) -> Self {
value.0
}
}
impl ::std::str::FromStr for ExtKey {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
static PATTERN: ::std::sync::LazyLock<::regress::Regex> =
::std::sync::LazyLock::new(|| {
::regress::Regex::new("^[a-z][a-z0-9-]*(\\.[a-z0-9-]+)+$").unwrap()
});
if PATTERN.find(value).is_none() {
return Err("doesn't match pattern \"^[a-z][a-z0-9-]*(\\.[a-z0-9-]+)+$\"".into());
}
Ok(Self(value.to_string()))
}
}
impl ::std::convert::TryFrom<&str> for ExtKey {
type Error = self::error::ConversionError;
fn try_from(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl ::std::convert::TryFrom<&::std::string::String> for ExtKey {
type Error = self::error::ConversionError;
fn try_from(
value: &::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl ::std::convert::TryFrom<::std::string::String> for ExtKey {
type Error = self::error::ConversionError;
fn try_from(
value: ::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl<'de> ::serde::Deserialize<'de> for ExtKey {
fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
where
D: ::serde::Deserializer<'de>,
{
::std::string::String::deserialize(deserializer)?
.parse()
.map_err(|e: self::error::ConversionError| {
<D::Error as ::serde::de::Error>::custom(e.to_string())
})
}
}
///`Payload`
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "$id": "https://trusttasks.org/spec/keys/sign/0.1",
/// "title": "Payload",
/// "type": "object",
/// "required": [
/// "algorithm",
/// "keyId",
/// "payload"
/// ],
/// "properties": {
/// "algorithm": {
/// "description": "Signature algorithm to use. MUST be compatible with the named key's `keyType`.",
/// "$ref": "#/definitions/SignAlgorithm"
/// },
/// "ext": {
/// "description": "Ecosystem-defined extension members per SPEC.md §4.5.1.",
/// "$ref": "#/definitions/Ext"
/// },
/// "keyId": {
/// "description": "Identifier of the key to sign with. The key MUST be `active`. Which keys a producer may name is the whole of this task's authorization story — the maintainer signs the bytes it is given without inspecting them.",
/// "type": "string",
/// "minLength": 1
/// },
/// "payload": {
/// "description": "The exact bytes to sign, base64url-encoded without padding. The maintainer signs these bytes verbatim: it does not parse, canonicalize, or wrap them.",
/// "type": "string"
/// }
/// },
/// "additionalProperties": false
///}
/// ```
/// </details>
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)]
#[serde(deny_unknown_fields)]
#[non_exhaustive]
pub struct Payload {
///Signature algorithm to use. MUST be compatible with the named key's `keyType`.
pub algorithm: SignAlgorithm,
///Ecosystem-defined extension members per SPEC.md §4.5.1.
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub ext: ::std::option::Option<Ext>,
///Identifier of the key to sign with. The key MUST be `active`. Which keys a producer may name is the whole of this task's authorization story — the maintainer signs the bytes it is given without inspecting them.
#[serde(rename = "keyId")]
pub key_id: PayloadKeyId,
///The exact bytes to sign, base64url-encoded without padding. The maintainer signs these bytes verbatim: it does not parse, canonicalize, or wrap them.
pub payload: ::std::string::String,
}
impl Payload {
pub fn builder() -> builder::Payload {
Default::default()
}
}
///Identifier of the key to sign with. The key MUST be `active`. Which keys a producer may name is the whole of this task's authorization story — the maintainer signs the bytes it is given without inspecting them.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "description": "Identifier of the key to sign with. The key MUST be `active`. Which keys a producer may name is the whole of this task's authorization story — the maintainer signs the bytes it is given without inspecting them.",
/// "type": "string",
/// "minLength": 1
///}
/// ```
/// </details>
#[derive(::serde::Serialize, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[serde(transparent)]
pub struct PayloadKeyId(::std::string::String);
impl ::std::ops::Deref for PayloadKeyId {
type Target = ::std::string::String;
fn deref(&self) -> &::std::string::String {
&self.0
}
}
impl ::std::convert::From<PayloadKeyId> for ::std::string::String {
fn from(value: PayloadKeyId) -> Self {
value.0
}
}
impl ::std::str::FromStr for PayloadKeyId {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
if value.chars().count() < 1usize {
return Err("shorter than 1 characters".into());
}
Ok(Self(value.to_string()))
}
}
impl ::std::convert::TryFrom<&str> for PayloadKeyId {
type Error = self::error::ConversionError;
fn try_from(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl ::std::convert::TryFrom<&::std::string::String> for PayloadKeyId {
type Error = self::error::ConversionError;
fn try_from(
value: &::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl ::std::convert::TryFrom<::std::string::String> for PayloadKeyId {
type Error = self::error::ConversionError;
fn try_from(
value: ::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl<'de> ::serde::Deserialize<'de> for PayloadKeyId {
fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
where
D: ::serde::Deserializer<'de>,
{
::std::string::String::deserialize(deserializer)?
.parse()
.map_err(|e: self::error::ConversionError| {
<D::Error as ::serde::de::Error>::custom(e.to_string())
})
}
}
///The success response to a keys/sign request. Carried in a Trust Task document whose type is https://trusttasks.org/spec/keys/sign/0.1#response.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "title": "Response",
/// "description": "The success response to a keys/sign request. Carried in a Trust Task document whose type is https://trusttasks.org/spec/keys/sign/0.1#response.",
/// "type": "object",
/// "required": [
/// "algorithm",
/// "keyId",
/// "signature"
/// ],
/// "properties": {
/// "algorithm": {
/// "$ref": "#/definitions/SignAlgorithm"
/// },
/// "ext": {
/// "$ref": "#/definitions/Ext"
/// },
/// "keyId": {
/// "description": "The key that produced the signature. Echoed so a response separated from its request is still attributable.",
/// "type": "string"
/// },
/// "signature": {
/// "description": "Signature bytes, base64url-encoded without padding.",
/// "type": "string"
/// }
/// },
/// "additionalProperties": false,
/// "$anchor": "response"
///}
/// ```
/// </details>
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)]
#[serde(deny_unknown_fields)]
#[non_exhaustive]
pub struct Response {
pub algorithm: SignAlgorithm,
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub ext: ::std::option::Option<Ext>,
///The key that produced the signature. Echoed so a response separated from its request is still attributable.
#[serde(rename = "keyId")]
pub key_id: ::std::string::String,
///Signature bytes, base64url-encoded without padding.
pub signature: ::std::string::String,
}
impl Response {
pub fn builder() -> builder::Response {
Default::default()
}
}
///`EdDSA` pairs with an `ed25519` key; `ES256` pairs with a `p256` key; `ML-DSA-44` and `ML-DSA-65` pair with `mldsa44` and `mldsa65` keys respectively. An `x25519` key performs key agreement and can sign nothing, so no algorithm here is valid for one. These are JOSE algorithm identifiers, externally owned, so they are carried verbatim and never re-cased (SPEC.md §4.10 rule 5); the ML-DSA names are those RFC 9964 registers in the JOSE Web Signature and Encryption Algorithms registry, which is why their hyphenated casing differs from the `keyType` values beside them — those are specification-defined. The set is expected to grow as algorithms are registered. The enumeration remains closed: an unrecognised algorithm is refused rather than silently substituted with a supported one.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "title": "SignAlgorithm",
/// "description": "`EdDSA` pairs with an `ed25519` key; `ES256` pairs with a `p256` key; `ML-DSA-44` and `ML-DSA-65` pair with `mldsa44` and `mldsa65` keys respectively. An `x25519` key performs key agreement and can sign nothing, so no algorithm here is valid for one. These are JOSE algorithm identifiers, externally owned, so they are carried verbatim and never re-cased (SPEC.md §4.10 rule 5); the ML-DSA names are those RFC 9964 registers in the JOSE Web Signature and Encryption Algorithms registry, which is why their hyphenated casing differs from the `keyType` values beside them — those are specification-defined. The set is expected to grow as algorithms are registered. The enumeration remains closed: an unrecognised algorithm is refused rather than silently substituted with a supported one.",
/// "type": "string",
/// "enum": [
/// "EdDSA",
/// "ES256",
/// "ML-DSA-44",
/// "ML-DSA-65"
/// ]
///}
/// ```
/// </details>
#[derive(
::serde::Deserialize,
::serde::Serialize,
Clone,
Copy,
Debug,
Eq,
Hash,
Ord,
PartialEq,
PartialOrd,
)]
#[non_exhaustive]
pub enum SignAlgorithm {
#[serde(rename = "EdDSA")]
EdDsa,
#[serde(rename = "ES256")]
Es256,
#[serde(rename = "ML-DSA-44")]
MlDsa44,
#[serde(rename = "ML-DSA-65")]
MlDsa65,
}
impl ::std::fmt::Display for SignAlgorithm {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
match *self {
Self::EdDsa => f.write_str("EdDSA"),
Self::Es256 => f.write_str("ES256"),
Self::MlDsa44 => f.write_str("ML-DSA-44"),
Self::MlDsa65 => f.write_str("ML-DSA-65"),
}
}
}
impl ::std::str::FromStr for SignAlgorithm {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
match value {
"EdDSA" => Ok(Self::EdDsa),
"ES256" => Ok(Self::Es256),
"ML-DSA-44" => Ok(Self::MlDsa44),
"ML-DSA-65" => Ok(Self::MlDsa65),
_ => Err("invalid value".into()),
}
}
}
impl ::std::convert::TryFrom<&str> for SignAlgorithm {
type Error = self::error::ConversionError;
fn try_from(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl ::std::convert::TryFrom<&::std::string::String> for SignAlgorithm {
type Error = self::error::ConversionError;
fn try_from(
value: &::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl ::std::convert::TryFrom<::std::string::String> for SignAlgorithm {
type Error = self::error::ConversionError;
fn try_from(
value: ::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
/// Types for composing complex structures.
pub mod builder {
#[derive(Clone, Debug)]
pub struct Payload {
algorithm: ::std::result::Result<super::SignAlgorithm, ::std::string::String>,
ext: ::std::result::Result<::std::option::Option<super::Ext>, ::std::string::String>,
key_id: ::std::result::Result<super::PayloadKeyId, ::std::string::String>,
payload: ::std::result::Result<::std::string::String, ::std::string::String>,
}
impl ::std::default::Default for Payload {
fn default() -> Self {
Self {
algorithm: Err("no value supplied for algorithm".to_string()),
ext: Ok(Default::default()),
key_id: Err("no value supplied for key_id".to_string()),
payload: Err("no value supplied for payload".to_string()),
}
}
}
impl Payload {
pub fn algorithm<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<super::SignAlgorithm>,
T::Error: ::std::fmt::Display,
{
self.algorithm = value
.try_into()
.map_err(|e| format!("error converting supplied value for algorithm: {e}"));
self
}
pub fn ext<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<::std::option::Option<super::Ext>>,
T::Error: ::std::fmt::Display,
{
self.ext = value
.try_into()
.map_err(|e| format!("error converting supplied value for ext: {e}"));
self
}
pub fn key_id<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<super::PayloadKeyId>,
T::Error: ::std::fmt::Display,
{
self.key_id = value
.try_into()
.map_err(|e| format!("error converting supplied value for key_id: {e}"));
self
}
pub fn payload<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<::std::string::String>,
T::Error: ::std::fmt::Display,
{
self.payload = value
.try_into()
.map_err(|e| format!("error converting supplied value for payload: {e}"));
self
}
}
impl ::std::convert::TryFrom<Payload> for super::Payload {
type Error = super::error::ConversionError;
fn try_from(value: Payload) -> ::std::result::Result<Self, super::error::ConversionError> {
Ok(Self {
algorithm: value.algorithm?,
ext: value.ext?,
key_id: value.key_id?,
payload: value.payload?,
})
}
}
impl ::std::convert::From<super::Payload> for Payload {
fn from(value: super::Payload) -> Self {
Self {
algorithm: Ok(value.algorithm),
ext: Ok(value.ext),
key_id: Ok(value.key_id),
payload: Ok(value.payload),
}
}
}
#[derive(Clone, Debug)]
pub struct Response {
algorithm: ::std::result::Result<super::SignAlgorithm, ::std::string::String>,
ext: ::std::result::Result<::std::option::Option<super::Ext>, ::std::string::String>,
key_id: ::std::result::Result<::std::string::String, ::std::string::String>,
signature: ::std::result::Result<::std::string::String, ::std::string::String>,
}
impl ::std::default::Default for Response {
fn default() -> Self {
Self {
algorithm: Err("no value supplied for algorithm".to_string()),
ext: Ok(Default::default()),
key_id: Err("no value supplied for key_id".to_string()),
signature: Err("no value supplied for signature".to_string()),
}
}
}
impl Response {
pub fn algorithm<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<super::SignAlgorithm>,
T::Error: ::std::fmt::Display,
{
self.algorithm = value
.try_into()
.map_err(|e| format!("error converting supplied value for algorithm: {e}"));
self
}
pub fn ext<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<::std::option::Option<super::Ext>>,
T::Error: ::std::fmt::Display,
{
self.ext = value
.try_into()
.map_err(|e| format!("error converting supplied value for ext: {e}"));
self
}
pub fn key_id<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<::std::string::String>,
T::Error: ::std::fmt::Display,
{
self.key_id = value
.try_into()
.map_err(|e| format!("error converting supplied value for key_id: {e}"));
self
}
pub fn signature<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<::std::string::String>,
T::Error: ::std::fmt::Display,
{
self.signature = value
.try_into()
.map_err(|e| format!("error converting supplied value for signature: {e}"));
self
}
}
impl ::std::convert::TryFrom<Response> for super::Response {
type Error = super::error::ConversionError;
fn try_from(value: Response) -> ::std::result::Result<Self, super::error::ConversionError> {
Ok(Self {
algorithm: value.algorithm?,
ext: value.ext?,
key_id: value.key_id?,
signature: value.signature?,
})
}
}
impl ::std::convert::From<super::Response> for Response {
fn from(value: super::Response) -> Self {
Self {
algorithm: Ok(value.algorithm),
ext: Ok(value.ext),
key_id: Ok(value.key_id),
signature: Ok(value.signature),
}
}
}
}
impl crate::Payload for Payload {
const TYPE_URI: &'static str = "https://trusttasks.org/spec/keys/sign/0.1";
const IS_PROOF_REQUIRED: bool = true;
const IS_ISSUED_AT_REQUIRED: bool = true;
const IS_RECIPIENT_REQUIRED: bool = true;
const PAYLOAD_SCHEMA: Option<&'static str> = Some(
"{\n \"$defs\": {\n \"Ext\": {\n \"additionalProperties\": true,\n \"description\": \"Vendor-namespaced extension object per SPEC.md §4.5.1. Each immediate key MUST be a reverse-DNS namespace; structure under each namespace is opaque to the framework.\",\n \"minProperties\": 1,\n \"propertyNames\": {\n \"pattern\": \"^[a-z][a-z0-9-]*(\\\\.[a-z0-9-]+)+$\"\n },\n \"title\": \"Ext\",\n \"type\": \"object\"\n },\n \"Response\": {\n \"$anchor\": \"response\",\n \"additionalProperties\": false,\n \"description\": \"The success response to a keys/sign request. Carried in a Trust Task document whose type is https://trusttasks.org/spec/keys/sign/0.1#response.\",\n \"properties\": {\n \"algorithm\": {\n \"$ref\": \"#/$defs/SignAlgorithm\"\n },\n \"ext\": {\n \"$ref\": \"#/$defs/Ext\"\n },\n \"keyId\": {\n \"description\": \"The key that produced the signature. Echoed so a response separated from its request is still attributable.\",\n \"type\": \"string\"\n },\n \"signature\": {\n \"description\": \"Signature bytes, base64url-encoded without padding.\",\n \"type\": \"string\"\n }\n },\n \"required\": [\n \"keyId\",\n \"signature\",\n \"algorithm\"\n ],\n \"title\": \"Keys Sign — response payload\",\n \"type\": \"object\"\n },\n \"SignAlgorithm\": {\n \"description\": \"`EdDSA` pairs with an `ed25519` key; `ES256` pairs with a `p256` key; `ML-DSA-44` and `ML-DSA-65` pair with `mldsa44` and `mldsa65` keys respectively. An `x25519` key performs key agreement and can sign nothing, so no algorithm here is valid for one. These are JOSE algorithm identifiers, externally owned, so they are carried verbatim and never re-cased (SPEC.md §4.10 rule 5); the ML-DSA names are those RFC 9964 registers in the JOSE Web Signature and Encryption Algorithms registry, which is why their hyphenated casing differs from the `keyType` values beside them — those are specification-defined. The set is expected to grow as algorithms are registered. The enumeration remains closed: an unrecognised algorithm is refused rather than silently substituted with a supported one.\",\n \"enum\": [\n \"EdDSA\",\n \"ES256\",\n \"ML-DSA-44\",\n \"ML-DSA-65\"\n ],\n \"title\": \"SignAlgorithm\",\n \"type\": \"string\"\n }\n },\n \"$id\": \"https://trusttasks.org/spec/keys/sign/0.1\",\n \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n \"additionalProperties\": false,\n \"properties\": {\n \"algorithm\": {\n \"$ref\": \"#/$defs/SignAlgorithm\",\n \"description\": \"Signature algorithm to use. MUST be compatible with the named key's `keyType`.\"\n },\n \"ext\": {\n \"$ref\": \"#/$defs/Ext\",\n \"description\": \"Ecosystem-defined extension members per SPEC.md §4.5.1.\"\n },\n \"keyId\": {\n \"description\": \"Identifier of the key to sign with. The key MUST be `active`. Which keys a producer may name is the whole of this task's authorization story — the maintainer signs the bytes it is given without inspecting them.\",\n \"minLength\": 1,\n \"type\": \"string\"\n },\n \"payload\": {\n \"description\": \"The exact bytes to sign, base64url-encoded without padding. The maintainer signs these bytes verbatim: it does not parse, canonicalize, or wrap them.\",\n \"type\": \"string\"\n }\n },\n \"required\": [\n \"keyId\",\n \"payload\",\n \"algorithm\"\n ],\n \"title\": \"Keys Sign — payload\",\n \"type\": \"object\"\n}\n",
);
}
impl crate::Payload for Response {
const TYPE_URI: &'static str = "https://trusttasks.org/spec/keys/sign/0.1#response";
const IS_PROOF_REQUIRED: bool = true;
const IS_ISSUED_AT_REQUIRED: bool = true;
const IS_RECIPIENT_REQUIRED: bool = true;
const PAYLOAD_SCHEMA: Option<&'static str> = Some(
"{\n \"$defs\": {\n \"Ext\": {\n \"additionalProperties\": true,\n \"description\": \"Vendor-namespaced extension object per SPEC.md §4.5.1. Each immediate key MUST be a reverse-DNS namespace; structure under each namespace is opaque to the framework.\",\n \"minProperties\": 1,\n \"propertyNames\": {\n \"pattern\": \"^[a-z][a-z0-9-]*(\\\\.[a-z0-9-]+)+$\"\n },\n \"title\": \"Ext\",\n \"type\": \"object\"\n },\n \"Response\": {\n \"$anchor\": \"response\",\n \"additionalProperties\": false,\n \"description\": \"The success response to a keys/sign request. Carried in a Trust Task document whose type is https://trusttasks.org/spec/keys/sign/0.1#response.\",\n \"properties\": {\n \"algorithm\": {\n \"$ref\": \"#/$defs/SignAlgorithm\"\n },\n \"ext\": {\n \"$ref\": \"#/$defs/Ext\"\n },\n \"keyId\": {\n \"description\": \"The key that produced the signature. Echoed so a response separated from its request is still attributable.\",\n \"type\": \"string\"\n },\n \"signature\": {\n \"description\": \"Signature bytes, base64url-encoded without padding.\",\n \"type\": \"string\"\n }\n },\n \"required\": [\n \"keyId\",\n \"signature\",\n \"algorithm\"\n ],\n \"title\": \"Keys Sign — response payload\",\n \"type\": \"object\"\n },\n \"SignAlgorithm\": {\n \"description\": \"`EdDSA` pairs with an `ed25519` key; `ES256` pairs with a `p256` key; `ML-DSA-44` and `ML-DSA-65` pair with `mldsa44` and `mldsa65` keys respectively. An `x25519` key performs key agreement and can sign nothing, so no algorithm here is valid for one. These are JOSE algorithm identifiers, externally owned, so they are carried verbatim and never re-cased (SPEC.md §4.10 rule 5); the ML-DSA names are those RFC 9964 registers in the JOSE Web Signature and Encryption Algorithms registry, which is why their hyphenated casing differs from the `keyType` values beside them — those are specification-defined. The set is expected to grow as algorithms are registered. The enumeration remains closed: an unrecognised algorithm is refused rather than silently substituted with a supported one.\",\n \"enum\": [\n \"EdDSA\",\n \"ES256\",\n \"ML-DSA-44\",\n \"ML-DSA-65\"\n ],\n \"title\": \"SignAlgorithm\",\n \"type\": \"string\"\n }\n },\n \"$ref\": \"#/$defs/Response\",\n \"$schema\": \"https://json-schema.org/draft/2020-12/schema\"\n}\n",
);
}
impl crate::RequestPayload for Payload {
type Response = Response;
}
/// The extended error codes this specification declares (SPEC §7.3 item 9,
/// §8.5), in declaration order. Empty when it declares none.
pub const ERROR_CODES: &[crate::DeclaredErrorCode] = &[
error_codes::INVALID_ARGUMENT,
error_codes::FAILED_PRECONDITION,
];
/// One constant per extended error code this specification declares
/// (SPEC §7.3 item 9), named for its local part.
///
/// Emit these rather than a string literal: the code is read from the
/// specification, so it cannot name a code the specification never
/// declared.
pub mod error_codes {
/// `keys:invalidArgument`
///
/// A payload member is well-formed against the schema but unusable for this request. See [category conventions](../../_shared/0.1/CONVENTIONS.md#1-family-error-codes).
///
/// Declared `retryable: false`.
pub const INVALID_ARGUMENT: crate::DeclaredErrorCode = crate::DeclaredErrorCode {
code: "keys:invalidArgument",
retryable: false,
};
/// `keys/sign:failedPrecondition`
///
/// The named key exists but its `status` is not `active`, so it cannot sign.
///
/// Declared `retryable: false`.
pub const FAILED_PRECONDITION: crate::DeclaredErrorCode = crate::DeclaredErrorCode {
code: "keys/sign:failedPrecondition",
retryable: false,
};
}
#[cfg(test)]
mod conformance {
//! Round-trip tests harvested from the spec's `spec.md`,
//! plus a `rejects_invalid_examples` test for any fixtures
//! in `payload.invalid-examples.json` (validate feature).
#[test]
fn request_example_1() {
const JSON: &str = "{\n \"id\": \"8f14e45f-ceea-467a-9f0a-2d0e6f7c5b31\",\n \"type\": \"https://trusttasks.org/spec/keys/sign/0.1\",\n \"issuer\": \"did:web:app.example\",\n \"recipient\": \"did:web:custodian.example\",\n \"issuedAt\": \"2026-07-31T09:00:00Z\",\n \"payload\": {\n \"keyId\": \"signing-key-1\",\n \"payload\": \"aGVsbG8tY2hhbGxlbmdl\",\n \"algorithm\": \"EdDSA\"\n }\n}\n";
let doc: crate::TrustTask<super::Payload> =
serde_json::from_str(JSON).expect("deserialize request example");
let rendered = serde_json::to_value(&doc).expect("re-serialize");
let expected: serde_json::Value = serde_json::from_str(JSON).expect("re-parse expected");
assert_eq!(rendered, expected, "request example failed round-trip");
}
#[test]
fn response_example_1() {
const JSON: &str = "{\n \"id\": \"9a24e45f-ceea-467a-9f0a-2d0e6f7c5b32\",\n \"type\": \"https://trusttasks.org/spec/keys/sign/0.1#response\",\n \"threadId\": \"8f14e45f-ceea-467a-9f0a-2d0e6f7c5b31\",\n \"issuer\": \"did:web:custodian.example\",\n \"recipient\": \"did:web:app.example\",\n \"issuedAt\": \"2026-07-31T09:00:01Z\",\n \"payload\": {\n \"keyId\": \"signing-key-1\",\n \"signature\": \"3q2-7wRLBt0FVsn6dR3ZLC8s0Xh0zvR3sVQ1qWvS0m8xJ7bB4kEwT9c1h2G5nP8aQ\",\n \"algorithm\": \"EdDSA\"\n }\n}\n";
let doc: crate::TrustTask<super::Response> =
serde_json::from_str(JSON).expect("deserialize response example");
let rendered = serde_json::to_value(&doc).expect("re-serialize");
let expected: serde_json::Value = serde_json::from_str(JSON).expect("re-parse expected");
assert_eq!(rendered, expected, "response example failed round-trip");
}
/// Each fixture in `payload.invalid-examples.json` MUST be
/// rejected by at least one of: serde deserialization, or
/// JSON-Schema validation under the `validate` feature. The
/// fixture file documents the producer-side bug class that
/// each payload exemplifies; this generated test pins it.
#[cfg(feature = "validate")]
#[test]
fn rejects_invalid_examples() {
use crate::validate::ValidatedPayload;
let fixtures: &[(&str, &str)] = &[
(
"Missing required `algorithm` — the custodian must never infer it from the key, because a key that supports more than one would then sign under whichever the implementation happened to prefer.",
"{\n \"keyId\": \"signing-key-1\",\n \"payload\": \"aGVsbG8tY2hhbGxlbmdl\"\n}",
),
(
"Missing required `payload` — nothing to sign.",
"{\n \"algorithm\": \"EdDSA\",\n \"keyId\": \"signing-key-1\"\n}",
),
(
"Unknown algorithm. The enum is closed so an unrecognised algorithm is refused rather than silently downgraded to a supported one.",
"{\n \"algorithm\": \"RS256\",\n \"keyId\": \"signing-key-1\",\n \"payload\": \"aGVsbG8tY2hhbGxlbmdl\"\n}",
),
(
"Empty `keyId` — additionalProperties/minLength guard against a request that names no key at all.",
"{\n \"algorithm\": \"EdDSA\",\n \"keyId\": \"\",\n \"payload\": \"aGVsbG8tY2hhbGxlbmdl\"\n}",
),
(
"Bare/unnamespaced ext key — SPEC §4.5.1 producer rule.",
"{\n \"algorithm\": \"EdDSA\",\n \"ext\": {\n \"no-dots\": {}\n },\n \"keyId\": \"signing-key-1\",\n \"payload\": \"aGVsbG8tY2hhbGxlbmdl\"\n}",
),
(
"Unknown top-level member. `additionalProperties: false` stops a caller smuggling a second key or a policy override past the schema.",
"{\n \"algorithm\": \"EdDSA\",\n \"fallbackKeyId\": \"signing-key-2\",\n \"keyId\": \"signing-key-1\",\n \"payload\": \"aGVsbG8tY2hhbGxlbmdl\"\n}",
),
];
for (i, (note, raw)) in fixtures.iter().enumerate() {
let value: serde_json::Value = match serde_json::from_str(raw) {
Ok(v) => v,
Err(_) => continue,
};
let serde_ok = serde_json::from_value::<super::Payload>(value.clone()).is_ok();
let schema_ok = super::Payload::validate_value(&value).is_ok();
assert!(
!(serde_ok && schema_ok),
"invalid-example #{} ({:?}) was accepted by both serde and JSON Schema; \
the fixture's stated failure class is no longer caught:\n{}",
i + 1,
note,
raw
);
}
}
}