Qoyod
Pricing

Knowledge Base

Technical Specification of the Digital Signature

This document is aimed at developers and integration engineers who build or audit e-invoice generation in line with the requirements of the Zakat, Tax and Customs Authority (ZATCA). Here we address the technical specification of the digital signature at the algorithm and field level: exactly which signature parameters the Authority mandates, the structure of the elements ds:SignedInfo andds:Reference andds:Transforms, the signed properties of XAdES , and the byte values of the algorithm identifiers.

Before proceeding, distinguish between three related but not identical documents. The conceptual document What is a digital signature and why does the Authority require it explains the idea of asymmetric encryption and the hash function theoretically. The document The technical specification of the cryptographic stamp explains where the signature element sits inside the XML file and how it is wrapped within UBLExtensions. This document, however, covers a different layer: the algorithm parameters themselves and the signed signature properties, that is what you sign, with which curve, which hash function, and which canonicalization identifier, byte by byte.

Scope of this document: the specification, not the concept or the placement

The architecture the Authority mandates for the e-invoice is built on the UBL 2.1 standard for the invoice, the XML Digital Signature (abbreviated XMLDSig) standard for the signature, and the XAdES profile for adding the signed properties. The signature we are discussing here is the establishment’s signature on the invoice, referred to in the Authority’s documents as the cryptographic stamp of the invoice. But the question this document answers is not where this signature sits, rather with which algorithm it is produced and with which parameters.

The difference is practical, not academic. If you copied the correct XML structure from the placement document but signed with the wrong curve or a non-matching hash function, the Authority’s validation engine would reject the invoice even though its form is sound. The specification here settles those parameters.

We assume you are dealing with the establishment’s cryptographic stamp certificate. The details of issuing and registering that certificate are found in The Compliance Cryptographic Stamp Identifier (CSID). In short: the certificate carries the establishment’s public key, and the corresponding private key is what performs the signing operation we describe below.

Why we separate the specification from the concept and the placement

Splitting the material across three documents may seem like documentation excess, but it reflects three different responsibilities within the development team. The security engineer cares about the concept: why the digital signature guarantees the invoice’s integrity and the non-repudiation of its issuer. The integration engineer cares about the placement: where to put the signature element within the UBL structure so the schema accepts it. The cryptography developer cares about the specification: with which algorithm, curve, and identifier the signature is actually produced so it passes validation.

Confusing these layers is the source of most failure cases. A team copies a correct XML structure from a reference, places it in the right location, but signs with the wrong parameters, ending up with an invoice that is sound in form yet rejected in substance. This document isolates that final layer and settles it, leaving no room for guesswork in the parameters the Authority’s engine reads literally.

We start from the algorithm, because it is the foundation of everything that follows. Every algorithm identifier, every digest element, and every signed property ultimately serves a single signing operation with specific parameters. Understanding those parameters first makes the rest of the structure comprehensible rather than memorized.

The approved algorithm: ECDSA on the secp256k1 curve

The Authority mandates a digital signature algorithm based on elliptic-curve cryptography. The algorithm is ECDSA (short for Elliptic Curve Digital Signature Algorithm), and the accompanying hash function is SHA-256. The approved elliptic curve is secp256k1, the same curve used in widely deployed cryptographic applications.

This choice is not a passing detail. The key pair in the establishment’s certificate must be generated on the secp256k1 curve specifically. If you generated the certificate with an RSA key pair or with a different elliptic curve such as secp256r1, the certificate will not comply with the e-invoicing requirements and validation will fail.

The length of the signature produced by ECDSA on this curve is much shorter than an RSA signature at an equivalent security level, and this is one of the reasons elliptic curves are adopted. From an implementation standpoint, however, what matters to you is configuring your cryptography library on these three parameters together: ECDSA for signing, SHA-256 for hashing, andsecp256k1 for the curve.

It helps here to clarify the difference between the signature algorithm, the curve, and the hash function, because developers often merge them mistakenly into a single parameter. ECDSA is the signing protocol that defines how the private key and the message digest are converted into a mathematical signature. The curve secp256k1 is the mathematical space over which the ECDSAoperations run, and it determines the key length and strength. The hash function SHA-256 produces the digest that is signed. Changing any of the three completely changes the output, which is why the Authority fixes all of them, not just one.

In practice, when generating the key pair for the certificate, the generation tool selects the curve. And when building the signature element, the library selects the signing and hash algorithms via the SignatureMethodidentifier. If you set the first and forget the second, or vice versa, a gap arises between the curve used in the certificate and the algorithm declared in the XML, and the engine rejects validation. Consistent configuration of both sides is an essential condition for a successful integration.

The approved digital signature parameters
The algorithms the Authority mandates for the digital signature.
Signature parameters

Signature algorithm: ECDSA

Curve: secp256k1

Hash function: SHA-256

Canonicalization: C14N 1.1

These parameters are fixed and mandatory in every invoice signature.

Algorithm identifiers as they appear in XML

Every algorithm in the XMLDSig standard is defined via a uniform identifier (URI) written explicitly in the XML. These identifiers are neither optional nor open to interpretation. The validation engine reads them literally to know which algorithm to validate with. The required values:

SignatureMethod Algorithm =
  "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256"

DigestMethod Algorithm (for the invoice element and for the signed properties) =
  "http://www.w3.org/2001/04/xmlenc#sha256"

CanonicalizationMethod / Transform Algorithm =
  "http://www.w3.org/2006/12/xml-c14n11"

The first identifier binds the signature to the ECDSA algorithm with SHA-256. The second identifier specifies the hash function used to compute the digest. The third identifier specifies the canonicalization method, which is the most critical topic in the entire specification, as we explain later.

The difference between the signature and the digest in this specification

Many developers confuse the hash function with the signature algorithm, even though they play two entirely different roles in this specification. The hash function SHA-256 produces a fixed-length digest from any input no matter how large, is a one-way operation that cannot be reversed, and uses no key. The signature algorithm ECDSA , on the other hand, uses the establishment’s private key to produce a value that only the holder of that key can produce, and which can be verified with the corresponding public key.

The order in the specification is precise: the SHA-256 digest is computed first for the invoice and for the signed properties, then these two digests are gathered inside ds:SignedInfo, then ds:SignedInfo itself is signed withECDSA. That is, the signature does not act on the bulky invoice content directly, but on a small element that holds its digests. This design is standard in XMLDSig, and its purpose is efficiency: signing a small element is faster than signing a full document, while preserving security because any change in the content changes its digest, which changes ds:SignedInfo , which breaks the signature.

This is why SHA-256 appears in two separate places: inside DigestMethod for each ds:Reference (to compute the digests), and inside the composite SignatureMethod identifier ecdsa-sha256 (because ECDSA itself hashes the canonicalized ds:SignedInfo before signing it). The function is one, but its role differs in each place. Awareness of this distinction explains why we see SHA-256 four times or more in a single invoice without erroneous repetition.

Signature type: enveloped with two references

The invoice signature in this specification is of the enveloped type (enveloped signature): the signature element lives inside the document it signs, not separate from it. And because it does, the circularity dilemma arose, which the transforms solved: the signature is part of the invoice, yet it cannot sign itself, so its region had to be excluded from the digest computation via ds:Transforms.

The two-reference design (ds:Reference twice) separates what is protected. The first reference protects the actual invoice content: parties, line items, tax, totals. The second reference protects the signature data itself: its time and its certificate. Had we relied on a single reference over the invoice, an attacker could change the signing time or point to a different certificate without breaking the signature. Separating the two references closes this gap and makes the XAdES data protected with the same strength that protects the invoice.

This understanding explains the order of steps during validation: the engine does not merely confirm the signature of ds:SignedInfo, it also recomputes the digests of the two references and compares them against the stored values. A valid signature over ds:SignedInfo that carries two forged digests is still rejected, because the comparison in the third and fourth steps of the validation sequence will expose the forgery.

The structure of ds:SignedInfo: what is actually signed

The element ds:SignedInfo is the heart of the signature. The rule many get wrong: the digital signature is not applied to the invoice directly, but to the ds:SignedInfo element after canonicalizing it. This element in turn contains digests of what you want to protect. That is, the signature protects the digests, and the digests protect the content.

consists of ds:SignedInfo three parts in order: the canonicalization method, then the signature method, then one or more ds:Referenceelements. Here is its required structure:

<ds:SignedInfo>
  <ds:CanonicalizationMethod
      Algorithm="http://www.w3.org/2006/12/xml-c14n11"/>
  <ds:SignatureMethod
      Algorithm="http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256"/>

  <ds:Reference Id="invoiceSignedData" URI="">
    <ds:Transforms>
      <ds:Transform Algorithm="http://www.w3.org/TR/1999/REC-xpath-19991116">
        <ds:XPath>not(//ancestor-or-self::ext:UBLExtensions)</ds:XPath>
      </ds:Transform>
      <ds:Transform Algorithm="http://www.w3.org/TR/1999/REC-xpath-19991116">
        <ds:XPath>not(//ancestor-or-self::cac:Signature)</ds:XPath>
      </ds:Transform>
      <ds:Transform Algorithm="http://www.w3.org/TR/1999/REC-xpath-19991116">
        <ds:XPath>not(//ancestor-or-self::cac:AdditionalDocumentReference[cbc:ID='QR'])</ds:XPath>
      </ds:Transform>
      <ds:Transform Algorithm="http://www.w3.org/2006/12/xml-c14n11"/>
    </ds:Transforms>
    <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
    <ds:DigestValue>...invoice digest in Base64 encoding...</ds:DigestValue>
  </ds:Reference>

  <ds:Reference URI="#xadesSignedProperties"
      Type="http://www.w3.org/2000/09/xmldsig#SignatureProperties">
    <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
    <ds:DigestValue>...signed properties digest in Base64 encoding...</ds:DigestValue>
  </ds:Reference>
</ds:SignedInfo>

Note the presence of two ds:Referenceelements. The first points to the entire invoice (an empty URI value means the root) after excluding what must not enter the computation. The second points to the signed properties via URI="#xadesSignedProperties". Both carry a SHA-256 digest stored in ds:DigestValue encoded in Base64. Base64.

The order of the elements is mandatory

The order of the elements inside ds:SignedInfo is not free. The CanonicalizationMethod must come first, then SignatureMethod, then the ds:Referenceelements. Any swap in the order changes the canonicalized representation of the element, and thus changes the digest, so validation fails. This is a point where those who build the XML manually rather than generating it automatically go wrong.

ds:Transforms: the transforms before computing the digest

Before computing the invoice digest in the first ds:Reference , the invoice passes through a chain of transforms (ds:Transforms). The purpose of these transforms is to exclude three parts that must not enter the digest computation, because they either contain the signature itself or are computed later.

The first three transforms are of type XPath, and their function is exclusion via an not(...):

  • expression. Exclude UBLExtensions: This part contains the signature element itself. Had it entered the digest computation, signing would have been impossible, because you would need the digest to sign, and need the signature to compute the digest.
  • expression. Exclude cac:Signature: A reference element for the signature inside the invoice body, excluded for the same reason.
  • Exclude the QR: The element AdditionalDocumentReference code reference whose identifier is QR is computed after signing, because the QR code includes the signature value itself.

The fourth and final transform is of the canonicalization type (c14n11), and it comes after the three exclusions to convert the result into its canonicalized form before computing the digest.

Signature transforms (Transforms) before hashing
How the invoice is prepared before computing the signed digest.
1

Exclude UBLExtensions

2

Exclude the signature element

3

Exclude the QR reference

4

C14N canonicalization → hashing

The transforms ensure a stable signature unaffected by the order of the elements.

Canonicalization and why it is the most critical step

Canonicalization is the conversion of an XML document into a uniform, unambiguous byte representation. The reason is that a single document can be written in multiple ways that are logically equivalent but byte-wise different: an indentation here, a blank line there, a different order of attributes, or a different way of declaring namespaces. Every byte difference produces a different SHA-256 digest, and validation fails.

The Authority mandates a specific canonicalization algorithm, namely Canonical XML 1.1, whose identifier is http://www.w3.org/2006/12/xml-c14n11. This algorithm settles every source of difference: it sorts attributes alphabetically, unifies whitespace encoding, propagates namespace declarations in a fixed way, and removes non-semantic differences.

The decisive rule: the signature is applied to the canonicalized form of ds:SignedInfo, not to its text as you wrote it. Therefore any later processing of the XML after signing, however innocent it seems, may break the signature if it changes the byte representation. A common mistake is reformatting the XML or adding indentation for readability after signing, which collapses validation.

Why canonicalization happens before signing and before the digest together

Canonicalization happens in two places: once on the invoice before computing its digest (the fourth transform in ds:Transforms), and once on ds:SignedInfo before signing it (via CanonicalizationMethod). Both places use the same algorithm c14n11. A correct understanding of this duality is what separates an implementation that passes validation from one that fails for no apparent reason.

The signed XAdES properties (SignedSignatureProperties)

The XMLDSig standard alone is not enough for the Authority’s requirements. On top of it a XAdES (short for XML Advanced Electronic Signatures) layer is added that binds additional signed data to the signature, most importantly the signing time and the digest of the signer’s certificate. This data is not outside the signature, but inside it: the second ds:Reference in ds:SignedInfo computes its digest, so it becomes protected by the signature itself.

The core element is xades:SignedSignatureProperties, and it holds two mandatory fields:

<xades:SignedSignatureProperties Id="xadesSignedProperties">
  <xades:SigningTime>2026-06-24T10:30:00Z</xades:SigningTime>
  <xades:SigningCertificate>
    <xades:Cert>
      <xades:CertDigest>
        <ds:DigestMethod
            Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
        <ds:DigestValue>...signer certificate digest...</ds:DigestValue>
      </xades:CertDigest>
      <xades:IssuerSerial>
        <ds:X509IssuerName>...issuer name...</ds:X509IssuerName>
        <ds:X509SerialNumber>...serial number...</ds:X509SerialNumber>
      </xades:IssuerSerial>
    </xades:Cert>
  </xades:SigningCertificate>
</xades:SignedSignatureProperties>

The first field SigningTime is a timestamp in the ISO 8601 format in Coordinated Universal Time (UTC), proving the moment of signing. The second field SigningCertificate carries a SHA-256 digest of the establishment’s certificate (not the full certificate), along with the issuer name and the serial number. Thus the signature proves it was produced with this specific certificate, so it cannot be swapped later without breaking the signature.

The value of Id="xadesSignedProperties" is not arbitrary. It is exactly the target that the second ds:Reference points to via URI="#xadesSignedProperties". Any spelling error in this identifier severs the link between the reference and the target, and the digest fails.

The signature value ds:SignatureValue and the public key ds:KeyInfo

After canonicalizing ds:SignedInfo and signing it with the establishment’s private key via ECDSA algorithm with SHA-256, the result is stored in the ds:SignatureValue encoded in Base64. Base64element. This is the actual signature value: the output of the ECDSA algorithm composed of the pair of values (r) and (s) after encoding it.

<ds:SignatureValue>
  ...ECDSA output over the canonicalized ds:SignedInfo, in Base64 encoding...
</ds:SignatureValue>

<ds:KeyInfo>
  <ds:X509Data>
    <ds:X509Certificate>
      ...the establishment certificate in X.509 format and Base64 encoding...
    </ds:X509Certificate>
  </ds:X509Data>
</ds:KeyInfo>

The element ds:KeyInfo carries the establishment’s public certificate in X.509format. This certificate contains the public key that the Authority’s engine uses to verify the signature value. And because the XAdES properties had already stored the digest of this certificate inside the signed region, any attempt to swap the certificate in ds:KeyInfo will conflict with the stored digest, so tampering is exposed.

Encoding: Base64, X.509 format, and byte order

The specification contains binary values that cannot be placed as-is inside XML text: the signature value, the digests, and the certificate. The adopted solution is Base64encoding, which converts binary data into text characters that are safe inside XML. Every ds:DigestValue value, every ds:SignatureValue value, and the certificate in ds:X509Certificate are written in this encoding.

The public certificate is stored in X.509format, the standard format for digital certificates, encoded inBase64 inside ds:X509Certificate. The certificate includes the establishment’s public key, the issuer information, the serial number, and the certificate’s validity. And because the XAdES properties stored the digest of this certificate in a signed region, the public key used in validation is fixed and does not accept substitution.

A subtle point in encoding the ECDSAoutput: the algorithm produces two numeric values (r) and (s), and they must be assembled in a consistent format before encoding them inBase64. A difference in assembly format between your library and the validation engine fails the match even though the signature is mathematically correct. Therefore it is important to use a library that implements the specification in the expected format, rather than assembling the two values in your own way.

Added to this is the configuration of namespaces. The prefixes ds: andxades: andext: andcac: andcbc: are not cosmetic, they bind each element to its correct namespace. The c14n11 canonicalization propagates these declarations in a fixed way, but declaring them poorly before canonicalization may produce a canonicalized form different from what the engine expects. Correct namespace configuration is an integral part of the specification even if it seems like a syntactic detail.

The Authority’s validation sequence step by step

To understand why the elements are ordered with this precision, follow what the Authority’s validation engine does when it receives the invoice:

  1. It reads ds:KeyInfo to extract the establishment’s certificate and its public key.
  2. It canonicalizes ds:SignedInfo with the c14n11algorithm, then verifies the ds:SignatureValue value using the public key andECDSA algorithm with SHA-256. If it fails here, the signature is invalid or the element was modified after signing.
  3. It recomputes the invoice digest after applying the four transforms, and compares it against the ds:DigestValue value in the first reference. If they differ, the invoice was modified.
  4. It recomputes the digest of xades:SignedProperties and compares it against the ds:DigestValue value in the second reference. If they differ, the properties were modified.
  5. It matches the certificate digest stored in xades:CertDigest with the certificate provided in ds:KeyInfo. If they differ, the certificate was swapped.

Any failure at any step rejects the invoice. Therefore soundness of form alone is not enough: the algorithms, the identifiers, and the canonicalization must match byte by byte.

The Authority’s validation sequence
How the Authority verifies the validity of the signature.
1

Read the certificate from KeyInfo

2

Verify SignatureValue

3

Recompute the invoice hash

4

Match the signed properties hash

Passing the sequence confirms that the invoice is signed and unmodified.

Common errors that fail validation

From the reality of e-invoicing integration, certain errors recur even though the XML looks sound:

  • Wrong curve: generating the key pair on secp256r1 instead of secp256k1. The certificate looks valid but does not match the specification.
  • Non-matching algorithm identifier: writing rsa-sha256 or a different hash identifier in SignatureMethod. The engine reads the identifier literally.
  • Reformatting after signing: adding indentation or reordering attributes on the XML after signing. The byte representation changes, so the digest collapses.
  • A flaw in the exclusions: forgetting to exclude UBLExtensions or the QR reference within ds:Transforms, so parts that must not enter the digest computation enter it.
  • Non-matching properties identifier: a mismatch between the Id value in SignedSignatureProperties and the second URI value in ds:Reference .

The practical rule: never edit the signed XML manually, and never apply any transformation to it after signing. Generate it automatically with a library that implements the full specification, then do not touch it.

Practical recommendations for the integration team

If you are building the integration yourself, start with the validation tool the Authority provides before actual submission. This tool applies the same validation sequence we described, and returns error messages that point to the step that failed. A failure message at the verification of ds:SignatureValue means an error in the algorithm or in the canonicalized form. And a failure message in matching the invoice digest means a flaw in the transforms or a modification after signing.

Test a simple invoice with the fewest line items first before moving to complex cases, to make it easier to isolate the cause of failure. Fix the generation environment so that no intermediate layer (such as proxy servers or formatting engines) interferes with the XML after signing it. And log the canonicalized form of ds:SignedInfo during development to compare it with what the engine computes on failure, as this is often the fastest way to discover a hidden byte difference.

Also monitor the validity of the cryptographic stamp certificate. An expired or unregistered certificate produces a mathematically correct signature that is nonetheless rejected at trust-chain validation. Managing the certificate lifecycle, from issuance to renewal, is part of a successful integration no less important than the correctness of the algorithm.

The simplest option for most establishments is not to build this layer themselves at all, but to use a system that handles it. Then your role is limited to registering the certificate with the Authority, and you leave XML generation, signing, canonicalization, and certificate management to the system.

Start today

Let Qoyod handle the digital signature for you

Qoyod generates your invoices in UBL format, signs them with the approved ECDSA algorithm, and manages the cryptographic stamp certificate automatically, so you comply with the Authority without writing a single line of XML.

Start your free trial and sign your invoices automatically

Qoyod’s role in the digital signature

Everything we described above happens automatically inside Qoyod. When issuing the invoice, Qoyod generates the XML file in line with the UBL 2.1standard, builds the ds:Signature element with its full structure, applies the transforms and canonicalization, signs ds:SignedInfo with the ECDSA algorithm with SHA-256 on the secp256k1curve, and adds the signed XAdES properties. Qoyod also manages the Compliance Cryptographic Stamp Identifier (CSID) certificate automatically, and stores the chain of invoice digests for validation purposes.

What remains on the establishment is registering its certificate with the Authority, a step Qoyod guides you through. Qoyod does not handle the registration on your behalf, and you do not need to compose the XML manually. Read the certificate details in The Compliance Cryptographic Stamp Identifier (CSID), and the details of the signature placement inside the file in The technical specification of the cryptographic stamp.

Frequently asked questions

What is the difference between this document and the technical specification of the cryptographic stamp?
The cryptographic stamp document explains where the signature element lives inside the XML file and how it is wrapped in UBLExtensions. This document explains with which algorithm and parameters the signature is produced: ECDSA, secp256k1, SHA-256, and the signed XAdES properties.

Why secp256k1 specifically and not secp256r1?
Because the Authority mandates this curve in its specification. The certificate key pair must be generated on it, and any other curve produces a non-compliant certificate that validation rejects.

Can I format the signed XML file to make it easier to read?
No. Any reformatting after signing changes the canonicalized byte representation, so the digest collapses and validation fails. Generate the file automatically and do not edit it after signing.

What happens if UBLExtensions enters the digest computation?
Producing the signature becomes impossible, because you would need the digest to sign, and need the signature located inside UBLExtensions to compute the digest. That is why the transforms in ds:Transforms exclude it.

Where is the actual signature value stored?
In the ds:SignatureValue element in Base64 encoding, and it is the output of the ECDSA algorithm applied to ds:SignedInfo after canonicalizing it.

Does Qoyod generate this signature automatically?
Yes. Qoyod builds the complete ds:Signature element, signs it with the approved parameters, and manages the CSID certificate automatically, so you do not need to write XML manually.

For the full picture of e-invoicing and its requirements, see Qoyod’s e-invoice page.

Guides

Continue your learning journey

Explore the rest of Qoyod’s guides, or start applying what you’ve learned.

Live webinars hosted by the Qoyod team to help you use the software easily and answer your questions.

Discover Qoyod’s latest updates, ongoing improvements, and new features in one place.

Our team is ready to help you and provide instant support for any issue you face, around the clock.