Skip to main content

Blind BBS Signatures
draft-irtf-cfrg-bbs-blind-signatures-03

Document Type Active Internet-Draft (cfrg RG)
Authors Vasilis Kalos , Greg M. Bernstein
Last updated 2026-06-26
Replaces draft-kalos-bbs-blind-signatures
RFC stream Internet Research Task Force (IRTF)
Intended RFC status Informational
Formats
Additional resources Mailing list discussion
Stream IRTF state Active RG Document
Consensus boilerplate Unknown
Document shepherd (None)
IESG IESG state I-D Exists
Telechat date (None)
Responsible AD (None)
Send notices to (None)
draft-irtf-cfrg-bbs-blind-signatures-03
CFRG                                                            V. Kalos
Internet-Draft                                                     MATTR
Intended status: Informational                              G. Bernstein
Expires: 28 December 2026                              Grotto Networking
                                                            26 June 2026

                          Blind BBS Signatures
              draft-irtf-cfrg-bbs-blind-signatures-03

Abstract

   This document defines an extension to the BBS Signature scheme that
   supports blind digital signatures, i.e., signatures over messages not
   known to the Signer.

Discussion Venues

   This note is to be removed before publishing as an RFC.

   Discussion of this document takes place on the Crypto Forum Research
   Group mailing list (cfrg@ietf.org), which is archived at
   https://mailarchive.ietf.org/arch/browse/cfrg.

   Source for this draft and an issue tracker can be found at
   https://github.com/cfrg/draft-irtf-cfrg-bbs-blind-signatures.

Status of This Memo

   This Internet-Draft is submitted in full conformance with the
   provisions of BCP 78 and BCP 79.

   Internet-Drafts are working documents of the Internet Engineering
   Task Force (IETF).  Note that other groups may also distribute
   working documents as Internet-Drafts.  The list of current Internet-
   Drafts is at https://datatracker.ietf.org/drafts/current/.

   Internet-Drafts are draft documents valid for a maximum of six months
   and may be updated, replaced, or obsoleted by other documents at any
   time.  It is inappropriate to use Internet-Drafts as reference
   material or to cite them other than as "work in progress."

   This Internet-Draft will expire on 28 December 2026.

Copyright Notice

   Copyright (c) 2026 IETF Trust and the persons identified as the
   document authors.  All rights reserved.

   This document is subject to BCP 78 and the IETF Trust's Legal
   Provisions Relating to IETF Documents (https://trustee.ietf.org/
   license-info) in effect on the date of publication of this document.
   Please review these documents carefully, as they describe your rights
   and restrictions with respect to this document.  Code Components
   extracted from this document must include Revised BSD License text as
   described in Section 4.e of the Trust Legal Provisions and are
   provided without warranty as described in the Revised BSD License.

Table of Contents

   1.  Introduction
     1.1.  Blind BBS Protocol Overview
     1.2.  Example Blind BBS Applications
     1.3.  Example Committed Disclosure Applications
     1.4.  Terminology
     1.5.  Notation
   2.  Conventions
   3.  BBS Signature Scheme Operations
   4.  Scheme Definition
     4.1.  Commitment Operations
       4.1.1.  Commitment Computation
       4.1.2.  Commitment Validation and Deserialization
     4.2.  Blind BBS Signatures Interface
       4.2.1.  Blind Signature Generation
       4.2.2.  Blind Signature Verification
       4.2.3.  Proof Generation
       4.2.4.  Proof Verification
     4.3.  Core Operations
       4.3.1.  Core Commitment Computation
       4.3.2.  Core Commitment Verification
       4.3.3.  Finalize Blind Sign
       4.3.4.  Core Proof Generation
       4.3.5.  Core Proof Verification
   5.  Utilities
     5.1.  Calculate B value
     5.2.  Blind Challenge Calculation
     5.3.  Proof Challenge Calculation
     5.4.  Serialize
       5.4.1.  Commitment with Proof to Octets
       5.4.2.  Octets to Commitment with Proof
       5.4.3.  Proof to Octets
       5.4.4.  Octets to Proof
   6.  Privacy Considerations
     6.1.  Total Number and Index of Committed Messages
   7.  Application Considerations
     7.1.  Input Validity Checks
   8.  Security Considerations
     8.1.  Prover Blind Factor
     8.2.  Key Binding
   9.  Ciphersuites
   10. Test Vectors
   11. IANA Considerations
   12. Normative References
   13. Informative References
   Appendix A.  Document History
   Authors' Addresses

1.  Introduction

   Blind signatures are cryptographic protocols that allow for a signer
   to create a signature over content without actually knowing the
   content.  They form a useful cryptographic primitive particularly in
   situations that are privacy sensitive.  The concept has existed for
   quite some time and is well explained in Chaum's 1985 popular article
   "Security without identification: transaction systems to make big
   brother obsolete" [Chaum85].  In [RFC9474], "RSA Blind Signatures",
   the RSA signature scheme was extended to provide for blind signing.
   In this document the BBS digital signature scheme, as defined in
   [I-D.irtf-cfrg-bbs-signatures], is extended to provide blind BBS
   signatures.

   Like BBS signatures blind BBS signatures work with a three party
   model of _Signer_, _Prover_, and _Verifier_. The blind BBS protocol
   defined here has the following useful properties:

   1.  Provides a signature over an ordered set of messages from the
       _Prover_ that are kept secret from the _Signer_ via a
       statistically hiding cryptographic commitment.
   2.  The _Signer_ will produce a signature for the _Prover_, only if
       the later can prove knowledge of the set of messages they choose.
       This will be done through a zero-knowledge proof-of-knowledge of
       the ordered set of secret _Prover_ messages.  The _Signer_ will
       not issue a signature without this proof of knowledge.
   3.  The Blind BBS signature produced is of the same size as current
       BBS signatures based on the same elliptic curve.
   4.  In addition to the _Prover_ provided secret messages, the
       _Signer_ can optionally sign over an additional ordered set of
       messages of their choosing.  This is sometimes know as a
       "partially blind" signature.
   5.  Using the Blind BBS signature created by the _Signer_ the
       _Prover_ can disclose any subset of both the secret _Prover_
       messages or the _Signer_'s messages and prove that these were in
       the signed sets.
   6.  Without knowledge of the ordered set of secret messages no
       selective disclosure proof can be generated even solely for a
       subset of the _Signer_ messages. (within the security assumptions
       of the BBS signature scheme).

   While the core BBS protocol allows a prover to either disclose or
   withhold a message from a verifier.  This specification allows for
   *committed disclosure* of a message [Vision2025].  In this case, the
   prover provides a commitment (computationally binding and perfectly
   hiding) to the message along with proof that the commitment
   corresponds to a particular message in the signature.

   The idea behind this committed-disclosure extension for BBS is that
   it also accommodates further zero knowledge proof (ZKP) extensions --
   e.g. range proofs or different pseudonyms -- in a modular, plug-and-
   play style.  Such extensions are out of scope of this specification.

1.1.  Blind BBS Protocol Overview

   The presented protocol, compared to the scheme defined in
   [I-D.irtf-cfrg-bbs-signatures], introduces an additional
   communication step between the _Prover_ and the _Signer_. An overview
   of the protocol is given below.

   1.  The _Prover_ will start by constructing a "hiding" commitment to
       the ordered set of messages they want to get a signature on
       (i.e., a commitment which reveals no information about the
       committed values), together with a proof of correctness of that
       commitment.
   2.  The _Prover_ will send the (commitment, proof) pair to the
       _Signer_, who, upon receiving the pair, will attempt to verify
       the commitment's proof of correctness.
   3.  If successful, they will use it in generating a blind BBS
       signature over the messages committed by the _Prover_, including
       the _Signer_'s own messages if any.
   4.  The _Signer_ will send the blind signature along with its
       additional ordered messages (if any) to the _Prover_
   5.  The _Prover_ can choose to selectively disclose or commit to any
       subset of either its own messages, kept secret from the _Signer_
       and messages provided by the _Signer_ in the signature.  They
       also furnish a ZKP that the these disclosed messages were
       included in the signature.
   6.  The _Verifier_ verifies the proof received from the _Prover_
       based on the _Signer_'s public key.

   Note: Cryptographic _commitments_ are used for two distinct purposes
   in this specification.  One, as a mechanism for the prover to get a
   blind signature from an signer, i.e., the prover is getting a
   signature over some data it is not revealing to the signer.  And,
   two, as mechanism to furnish less information from the prover to the
   verifier by providing a commitment along with a ZKP about that
   commitment.  For example, instead of providing a date of birth, the
   prover provides a commitment to that date of birth along with ZKP
   that indicates that the provers age lies in a particular range.

   Below is a basic diagram describing the main entities involved in the
   scheme.

    (3) Blind Sign                                          (1) Commit
        +-----                                                +-----
        |    |                                                |    |
        |    |                                                |    |
        |   \ /                                               |   \ /
     +----------+                                          +-----------+
     |          |                                          |           |
     |          |                                          |           |
     |          |<-(2)* Commitment + Proof of Correctness--|           |
     |  Signer  |                                          |   Prover  |
     |          |--(4)* Send signature + msgs + coms------>|           |
     |          |                                          |           |
     |          |                                          |           |
     +----------+                                          +-----------+
                                                                 |
                                                                 |
                                                                 |
                                                         (5)* Send proof
                                                                 +
                                                          disclosed msgs
                                                                 |
                                                                 |
                                                                \ /
                                                           +-----------+
                                                           |           |
                                                           |           |
                                                           |           |
                                                           |  Verifier |
                                                           |           |
                                                           |           |
                                                           |           |
                                                           +-----------+
                                                              |   / \
                                                              |    |
                                                              |    |
                                                              +-----
                                                         (6) ProofVerify

      Figure 1: Basic diagram capturing the main entities involved in
                             using the scheme.

   *Note* The protocols implied by the items annotated by an asterisk
   are out of scope for this specification

   This document, in addition to defining the operation for creating and
   verifying a commitment, also details a core signature generation
   operation, different from the one presented in
   [I-D.irtf-cfrg-bbs-signatures], meant to handle the computation of
   the blind signature.  The document will also define a new BBS
   Interface, which is needed to handle the different inputs, i.e.,
   messages committed by the _Prover_ or chosen by the Signer etc.. The
   signature verification and proof generation and verification core
   cryptographic operations however, will work as described in
   [I-D.irtf-cfrg-bbs-signatures].

1.2.  Example Blind BBS Applications

   By allowing the _Prover_ to acquire a valid signature over messages
   not known to the Signer, blind signatures address some limitations of
   their plain digital signature counterparts.  In the BBS Signature
   scheme, knowledge of a valid signature and set of signed messages
   allows generation of BBS proofs.  As a result, a signature compromise
   (for example by a Signer database leakage, a phishing attack etc.,)
   can lead to impersonation of the _Prover_ by malicious actors
   (especially in cases involving "long-lived" signatures, as in digital
   credentials applications etc.,).  Using Blind BBS Signatures on the
   other hand, the _Prover_ can commit to a secret message (for example,
   a private key) before issuance, guaranteeing that no one will be able
   to generate a valid BBS proof without knowledge of that secret
   message.

   Furthermore, applications like Privacy Pass
   ([I-D.ietf-privacypass-protocol]) may require a signature to be
   "scoped" to a specific audience or session (as to require "fresh"
   signatures for different sessions etc.,).  However, simply sending an
   audience or session identifier to the Signer (to be included in the
   signature), will compromise the privacy guarantees that these
   applications try to enforce.  Using blind signing, the Prover will be
   able to require signatures bound to those values, without having to
   reveal them to the Signer.

1.3.  Example Committed Disclosure Applications

   Privacy is enhanced via the *committed disclosure* mechanism along
   with an external ZKP proof of some predicate.  In this case rather
   than selectively disclosing a signed message to the verifier the
   prover provides a (computationally binding and perfectly hiding)
   commitment along with a ZKP concerning some aspect (the predicate) of
   the committed value.  This ZKP actually comes in two parts.  One part
   is specified in this specification and proves that the given
   commitment(s) corresponds to the corresponding messages signed by the
   signer.  The second part is an additional ZKP, not specified here,
   that proves some predicate about the committed values.

   As discussed in [Vision2025] this allows for the modular addition for
   proving (1) possession of a device key, (2) range proofs and (3)
   pseudonyms.  While in [LegacyBinding2026] additional more efficient
   proof of possession of a (hardware) device key are given.  These
   would all be implemented as an additional ZKP along with the ZKP
   specified here.

1.4.  Terminology

   Terminology defined by [I-D.irtf-cfrg-bbs-signatures] applies to this
   draft.

   Additionally, the following terminology is used throughout this
   document:

   blind_signature  The blind digital signature output.
   commitment  A point of G1, representing a Pedersen commitment ([P91])
      constructed over a vector of messages, as described e.g., in
      [BG18].
   committed_messages  A list of messages committed by the Prover to a
      commitment.
   commitment_proof  A zero knowledge proof of correctness of a
      commitment, consisting of a scalar value, a possibly empty set of
      scalars (of length equal to the number of committed_messages, see
      above) and another scalar, in that order.
   secret_prover_blind  A random scalar used to blind (i.e., randomize)
      the commitment constructed by the prover.
   signer_blind  A random scalar used by the signer to optionally re-
      blind the received commitment.
   NONE  An empty function input indicator, used to specify that one of
      the OPTIONAL inputs of a procedure is not provided by the calling
      operation.

1.5.  Notation

   Notation defined by [I-D.irtf-cfrg-bbs-signatures] applies to this
   draft.

   Additionally, the following notation and primitives are used:

   list.append(elements)  Append either a single element or a list of
      elements to the end of a list, maintaining the same order of the
      list's elements as well as the appended elements.  For example,
      given list = [a, b, c] and elements = [d, a], the result of
      list.append(elements) will be [a, b, c, d, a].

2.  Conventions

   The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
   "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and
   "OPTIONAL" in this document are to be interpreted as described in BCP
   14 [RFC2119] [RFC8174] when, and only when, they appear in all
   capitals, as shown here.

3.  BBS Signature Scheme Operations

   This document makes use of various operations defined by the BBS
   Signature Scheme document [I-D.irtf-cfrg-bbs-signatures].  For
   clarity, whenever an operation will be used defined in
   [I-D.irtf-cfrg-bbs-signatures], it will be prefixed by "BBS." (e.g.,
   "BBS.CoreProofGen" etc.).  More specifically, the operations used are
   the following:

   *  BBS.CoreVerify: Refers to the CoreVerify operation defined in
      Section 3.6.2 (https://www.ietf.org/archive/id/draft-irtf-cfrg-
      bbs-signatures-05.html#name-coreverify) of
      [I-D.irtf-cfrg-bbs-signatures].
   *  BBS.CoreProofGen: Refers to the CoreProofGen operation defined in
      Section 3.6.3 (https://www.ietf.org/archive/id/draft-irtf-cfrg-
      bbs-signatures-05.html#name-coreproofgen) of
      [I-D.irtf-cfrg-bbs-signatures].
   *  BBS.create_generators: Refers to the create_generators operation
      defined in Section 4.1.1 (https://www.ietf.org/archive/id/draft-
      irtf-cfrg-bbs-signatures-05.html#name-generators-calculation) of
      [I-D.irtf-cfrg-bbs-signatures].
   *  BBS.messages_to_scalars: Refers to the messages_to_scalars
      operation defined in Section 4.1.2
      (https://www.ietf.org/archive/id/draft-irtf-cfrg-bbs-signatures-
      05.html#name-messages-to-scalars) of
      [I-D.irtf-cfrg-bbs-signatures].
   *  BBS.calculate_random_scalars: Refers to the
      calculate_random_scalars operation defined in Section 4.2.1
      (https://www.ietf.org/archive/id/draft-irtf-cfrg-bbs-signatures-
      05.html#name-random-scalars) of [I-D.irtf-cfrg-bbs-signatures].
   *  BBS.hash_to_scalar: Refers to the hash_to_scalar operation defined
      in Section 4.2.2 (https://www.ietf.org/archive/id/draft-irtf-cfrg-
      bbs-signatures-05.html#name-hash-to-scalar) of
      [I-D.irtf-cfrg-bbs-signatures].
   *  BBS.calculate_domain: Refers to the calculate_domain operation
      defined in Section 4.2.3 (https://www.ietf.org/archive/id/draft-
      irtf-cfrg-bbs-signatures-07.html#domain-calculation) of
      [I-D.irtf-cfrg-bbs-signatures].

4.  Scheme Definition

4.1.  Commitment Operations

4.1.1.  Commitment Computation

   This operation is used by the Prover to create a commitment to a set
   of messages (committed_messages), that they intend to include in the
   blind signature.  Note that this operation returns both the
   serialized combination of the commitment and its proof of correctness
   (commitment_with_proof), as well as the random scalar used to blind
   the commitment (secret_prover_blind).

(commitment_with_proof, secret_prover_blind) = Commit(
                                                   committed_messages,
                                                   api_id)

Inputs:

- committed_messages (OPTIONAL), a vector of octet strings. If not
                                 supplied it defaults to the empty
                                 array ("()").
- api_id (OPTIONAL), octet string. If not supplied it defaults to the
                     empty octet string ("").

Outputs:

- (commitment_with_proof, secret_prover_blind), a tuple comprising from
                                                an octet string and a
                                                random scalar in that
                                                order.

Procedure:

1. committed_message_scalars = BBS.messages_to_scalars(
                                             committed_messages, api_id)

2. blind_generators = BBS.create_generators(
                                  length(committed_message_scalars) + 1,
                                  "BLIND_" || api_id)

3. return CoreCommit(committed_message_scalars,
                             blind_generators, api_id)

4.1.2.  Commitment Validation and Deserialization

   The following is a helper operation used by the BlindSign procedure
   (Section 4.2.1) to validate an optional commitment.  If a commitment
   is not supplied, or if it is the Identity_G1, the following operation
   will return the Identity_G1 as the "default" commitment point, which
   will be ignored by all computations during BlindSign.

commit = deserialize_and_validate_commit(commitment_with_proof,
                                               blind_generators, api_id)

Inputs:

- commitment_with_proof (OPTIONAL), octet string. If it is not supplied
                                    it defaults to the empty octet
                                    string ("").
- blind_generators (OPTIONAL), vector of points of G1. If it is not
                               supplied it defaults to the empty set
                               ("()").
- api_id (OPTIONAL), octet string. If not supplied it defaults to the
                     empty octet string ("").

Outputs:

- commit, a point of G1; or INVALID.

Procedure:

1. if commitment_with_proof is the empty string (""), return Identity_G1

2. com_res = octets_to_commitment_with_proof(commitment_with_proof)
3. if com_res is INVALID, return INVALID

4. (commit, commit_proof) = com_res
5. if length(commit_proof[1]) + 1 != length(blind_generators),
                                                          return INVALID

6. validation_res = CoreCommitVerify(commit, commit_proof,
                                               blind_generators, api_id)
7. if validation_res is INVALID, return INVALID
8. return commit

4.2.  Blind BBS Signatures Interface

   The following section defines a BBS Interface for blind BBS
   signatures.  The identifier of the Interface is defined as
   ciphersuite_id || BLIND_H2G_HM2S_, where ciphersuite_id the unique
   identifier of the BBS ciphersuite used, as is defined in Section 6
   (https://www.ietf.org/archive/id/draft-irtf-cfrg-bbs-signatures-
   03.html#name-ciphersuites) of [I-D.irtf-cfrg-bbs-signatures]).  Each
   BBS Interface MUST define operations to map the input messages to
   scalar values and to create the generator set, required by the core
   operations.  The input messages to the defined Interface will be
   mapped to scalars using the messages_to_scalars operation defined in
   Section 4.1.2 (https://www.ietf.org/archive/id/draft-irtf-cfrg-bbs-
   signatures-05.html#name-messages-to-scalars) of
   [I-D.irtf-cfrg-bbs-signatures].  The generators will be created using
   the create_generators operation defined in Section 4.1.1
   (https://www.ietf.org/archive/id/draft-irtf-cfrg-bbs-signatures-
   05.html#name-generators-calculation) of
   [I-D.irtf-cfrg-bbs-signatures].

   Other than the BlindSign operation defined in Section 4.2.1, which
   uses the FinalizeBlindSign procedure, defined in Section 4.3.3, all
   other interface operations defined in this section use the core
   operations defined in Section 3.6 (https://www.ietf.org/archive/id/
   draft-irtf-cfrg-bbs-signatures-05.html#name-core-operations) of
   [I-D.irtf-cfrg-bbs-signatures].

4.2.1.  Blind Signature Generation

   This operation returns a BBS blind signature from a secret key (SK),
   over a header, a set of messages and optionally a commitment value
   (see Section 1.4).  If supplied, the commitment value must be
   accompanied by its proof of correctness (commitment_with_proof, as
   outputted by the Commit operation defined in Section 4.1.1).

   The BlindSign operation makes use of the FinalizeBlindSign procedure
   defined in Section 4.3.3 and the B_calculate procedure defined in
   Section 5.1.  The B_calculate is defined to return an array of
   elements, to establish extendability of the scheme by allowing the
   B_calculate operation to return more elements than just the point to
   be signed.

blind_signature = BlindSign(SK, PK, commitment_with_proof, header,
                                                               messages)

Inputs:

- SK (REQUIRED), a secret key in the form outputted by the KeyGen
                 operation.
- PK (REQUIRED), an octet string of the form outputted by SkToPk
                 provided the above SK as input.
- commitment_with_proof (OPTIONAL), an octet string, representing a
                                    serialized commitment and
                                    commitment_proof, as the first
                                    element outputted by the Commit
                                    operation. If not supplied, it
                                    defaults to the empty string ("").
- header (OPTIONAL), an octet string containing context and application
                     specific information. If not supplied, it defaults
                     to an empty string ("").
- messages (OPTIONAL), a vector of octet strings. If not supplied, it
                       defaults to the empty array ("()").

Parameters:

- api_id, the octet string ciphersuite_id || "BLIND_H2G_HM2S_", where
          ciphersuite_id is defined by the ciphersuite and
          "BLIND_H2G_HM2S_"is an ASCII string composed of 15 bytes.
- (octet_point_length, octet_scalar_length), defined by the ciphersuite.

Outputs:

- blind_signature, a blind signature encoded as an octet string; or
                   INVALID.

Deserialization:

1. L = length(messages)

// calculate the number of blind generators used by the commitment,
// if any.
2. M = length(commitment_with_proof)
3. if M != 0, M = M - octet_point_length - 2 * octet_scalar_length
4. M = M / octet_scalar_length
5. if M < 0, return INVALID

Procedure:

1.  generators = BBS.create_generators(L + 1, api_id)
2.  blind_generators = BBS.create_generators(M + 1, "BLIND_" || api_id)

3.  commit = deserialize_and_validate_commit(commitment_with_proof,
                                               blind_generators, api_id)
4.  if commit is INVALID, return INVALID

5.  message_scalars = BBS.messages_to_scalars(messages, api_id)

6.  res = B_calculate(generators, commit, message_scalars)
7.  if res is INVALID, return INVALID
8.  (B) = res

9.  blind_sig = FinalizeBlindSign(SK,
                                  PK,
                                  B,
                                  generators,
                                  blind_generators,
                                  header,
                                  api_id)
10. if blind_sig is INVALID, return INVALID
11. return blind_sig

4.2.2.  Blind Signature Verification

   This operation validates a blind BBS signature (signature), given the
   Signer's public key (PK), a header (header), a set of messages
   (messages), including first the messages chosen by the Issuer and
   then the ones chosen (and committed to) by the Prover and if used,
   the secret_prover_blind as returned by the Commit operation
   (Section 4.1.1).

   This operation makes use of the CoreVerify operation as defined in
   Section 3.6.2 (https://www.ietf.org/archive/id/draft-irtf-cfrg-bbs-
   signatures-05.html#name-coreverify) of
   [I-D.irtf-cfrg-bbs-signatures].

result = VerifyBlindSign(PK, signature, header, messages,
                          issuer_known_messages_no, secret_prover_blind)

Inputs:

- PK (REQUIRED), an octet string of the form outputted by the SkToPk
                 operation.
- signature (REQUIRED), an octet string of the form outputted by the
                        Sign operation.
- header (OPTIONAL), an octet string containing context and application
                     specific information. If not supplied, it defaults
                     to an empty string.
- messages (OPTIONAL), a vector of octet strings. If not supplied, it
                       defaults to the empty array "()".
- issuer_known_messages_no (OPTIONAL), a non-negative integer. If not
                                       supplied, it defaults to 0.
- secret_prover_blind (OPTIONAL), a scalar value. If not supplied it
                                  defaults to zero "0".

Parameters:

- api_id, the octet string ciphersuite_id || "BLIND_H2G_HM2S_", where
          ciphersuite_id is defined by the ciphersuite and
          "BLIND_H2G_HM2S_"is an ASCII string composed of 15 bytes.

Outputs:

- result: either VALID or INVALID

Deserialization:

1. L = length(messages)

Procedure:

1. generators = BBS.create_generators(issuer_known_messages_no, api_id)
2. blind_generators = BBS.create_generators(
                       L - issuer_known_messages_no, "BLIND_" || api_id)

3. message_scalars = BBS.messages_to_scalars(messages, api_id)

4. res = BBS.CoreVerify(PK,
                        signature,
                        generators.append(blind_generators),
                        header,
                        message_scalars.append(secret_prover_blind),
                        api_id)
5. return res

4.2.3.  Proof Generation

   This operation creates a BBS proof, which is a zero-knowledge, proof-
   of-knowledge, of a BBS signature, while optionally disclosing any
   subset of the signed messages (either chosen by the Issuer or
   committed by the Prover).  In addition, this operation can generate
   commitments to un-revealed messages and include with the BBS proof
   that these commitments correspond to specific un-revealed messages.
   These commitments can be used in subsequent ZKPs outside the scope of
   this specification .

   When this operation furnishes disclosed commitment values it will
   also return an additional bundle of information for use in external
   ZKPs [Vision2025].  This _add_zkp_info_ includes the disclosed
   commitments and the random scalars used to produce those commitments.
   The _add_zkp_info_ should never be exposed, i.e., it is NOT to be
   sent sent to the verifier.

   The operation will accept a set of messages (messages), including
   first the messages chosen by the Issuer and then the ones chosen (and
   committed to) by the Prover.

   Furthermore, the operation also expects the secret_prover_blind (as
   returned from the Commit operation defined in Section 4.1.1) value.
   If the BBS signature is generated using a commitment value, then the
   secret_prover_blind returned by the Commit operation used to generate
   the commitment should be provided to the ProofGen operation
   (otherwise the resulting proof will be invalid).

   This operation makes use of the CoreProofGen operation as defined in
   Section 4.3.4.

   The operation will also accept a map message_disclosures between each
   message (including both the ones known by the issuer and the ones
   known only by the prover) and one of the three values DISCLOSE, HIDE
   and COMMIT.  A {msg: DISCLOSE} (key, value) pair indicates that the
   msg will be revealed to the Verifier.  Correspondingly, a {msg: HIDE}
   (key, value) pair indicates that the msg will not be disclosed to the
   Verifier.  Finally, a {msg: COMMIT} (key, value) pair indicates that
   only a commitment to the msg will be disclosed to the Verifier.

   An example of the message_disclosures input map is the following,

   message_disclosures = {
     issuer_known_msg_1: DISCLOSE,
     issuer_known_msg_2: HIDE,
     issuer_known_msg_3: HIDE,
     issuer_known_msg_4: COMMIT,
     issuer_known_msg_5: COMMIT,
     issuer_known_msg_6: DISCLOSE,
     prover_known_msg_1: DISCLOSE,
     prover_known_msg_2: COMMIT,
     prover_known_msg_3: DISCLOSE
   }

[proof, add_zkp_info] = BlindProofGen(PK,
                      signature,
                      header,
                      ph,
                      messages,
                      issuer_known_messages_no,
                      message_disclosures,
                      secret_prover_blind)

Inputs:

- PK (REQUIRED), an octet string of the form outputted by the SkToPk
                 operation.
- signature (REQUIRED), an octet string of the form outputted by the
                        Sign operation.
- header (OPTIONAL), an octet string containing context and application
                     specific information. If not supplied, it defaults
                     to an empty string.
- ph (OPTIONAL), an octet string containing the presentation header. If
                 not supplied, it defaults to an empty string.
- messages (OPTIONAL), a vector of octet strings. If not supplied, it
                       defaults to the empty array "()".
- issuer_known_messages_no (OPTIONAL), a non-negative integer. If not
                                       supplied, it defaults to 0.
- message_disclosures (OPTIONAL), a map between octet strings and one
                                   of the DISCLOSE, HIDE or COMMIT
                                   values. If not supplied, it defaults
                                   to the empty map "{}".
- secret_prover_blind (OPTIONAL), a scalar value. If not supplied it
                                  defaults to zero "0".

Parameters:

- api_id, the octet string ciphersuite_id || "BLIND_H2G_HM2S_", where
          ciphersuite_id is defined by the ciphersuite and
          "BLIND_H2G_HM2S_"is an ASCII string composed of 15 bytes.

Outputs:

- proof, an octet string; or INVALID.
- add_zkp_info, a structure containing an array of committed disclosure
                commitments, and the array of random scalars used to 
                create those commitments.

Deserialization:

1. L = length(messages)
2. if length(message_disclosures) != L, return INVALID
3. if issuer_known_messages_no > L, return INVALID

4. let disclosed_indexes be the integers i in 0..length(messages) so
   that if msg = messages[i], messagesDisclosures[msg] = disclose, in
   accenting order.
5. let commit_indexes be the integers i in 0..length(messages) so
   that if msg = messages[i], messagesDisclosures[msg] = commit, in
   accenting order.

Procedure:

1. generators = BBS.create_generators(issuer_known_messages_no, api_id)
2. blind_generators = BBS.create_generators(
                       L - issuer_known_messages_no, "BLIND_" || api_id)

3. message_scalars = BBS.messages_to_scalars(messages, api_id)

4. proof = CoreProofGen(PK,
                        signature,
                        generators.append(blind_generators),
                        header,
                        ph,
                        message_scalars.append(secret_prover_blind),
                        disclosed_indexes,
                        commit_indexes,
                        api_id)
5. return proof

4.2.4.  Proof Verification

   The ProofVerify operation validates a BBS proof, given the Signer's
   public key (PK), a header and presentation header values, two arrays
   of disclosed messages (the ones provided by the Signer and the ones
   committed by the prover) and two corresponding arrays of indexes that
   those messages had in the original vectors of signed messages.  In
   addition, the BlindProofVerify operation defined in this section
   accepts the integer issuer_known_messages_no, representing the total
   number of signed messages known by the Signer.

   This operation makes use of the CoreProofVerify operation as defined
   in Section 4.3.5.

result = BlindProofVerify(PK,
                          proof,
                          header,
                          ph,
                          issuer_known_messages_no,
                          disclosed_messages)

Inputs:

- PK (REQUIRED), an octet string of the form outputted by the SkToPk
                 operation.
- proof (REQUIRED), an octet string of the form outputted by the
                    ProofGen operation.
- header (OPTIONAL), an optional octet string containing context and
                     application specific information. If not supplied,
                     it defaults to the empty octet string ("").
- ph (OPTIONAL), an octet string containing the presentation header. If
                 not supplied, it defaults to the empty octet
                 string ("").
- issuer_known_messages_no (OPTIONAL), a non-negative integer. If not
                                       supplied, it defaults to 0.
- disclosed_messages (OPTIONAL), a vector of octet strings. If not
                                 supplied, it defaults to the empty
                                 array ("()").

Parameters:

- api_id, the octet string ciphersuite_id || "H2G_HM2S_", where
          ciphersuite_id is defined by the ciphersuite and "H2G_HM2S_"is
          an ASCII string comprised of 9 bytes.
- (octet_point_length, octet_scalar_length), defined by the ciphersuite.

Outputs:

- result, either VALID or INVALID.

Deserialization:

1. bbs_proof_len = OS2IP(proof[0..7])
2. undisclosed_msgs_no = bbs_proof_len
                           - 3 * octet_point_length
                           - 4 * octet_scalar_length
3. total_msgs_no = undisclosed_msgs_no + length(disclosed_messages)

Procedure:

1. generators = BBS.create_generators(issuer_known_messages_no, api_id)
2. blind_generators = BBS.create_generators(
                               total_msgs_no - issuer_known_messages_no,
                               "BLIND_" || api_id)

3. message_scalars = BBS.messages_to_scalars(disclosed_messages, api_id)

4. result = CoreProofVerify(
                        PK,
                        proof,
                        generators.append(blind_generators).append(Q_2),
                        header,
                        ph,
                        message_scalars,
                        api_id)
5. return result

4.3.  Core Operations

4.3.1.  Core Commitment Computation

(commit_with_proof, secret_prover_blind) = CoreCommit(blind_generators,
                                      committed_message_scalars, api_id)

Inputs:

- blind_generators (REQUIRED), vector of pseudo-random points in G1.
- committed_message_scalars (OPTIONAL), a vector of scalars. If not 
                        supplied, it defaults to the empty array ("()").
- api_id (OPTIONAL), an octet string. If not supplied it defaults to the
                     empty octet string ("").

Deserialization:

1. M = length(committed_messages)
2. if length(blind_generators) != M + 1, return INVALID
3. (Q_2, J_1, ..., J_M) = blind_generators
4. (msg_1, ..., msg_M) = committed_message_scalars
Procedure:

1. (secret_prover_blind, s~, m~_1, ..., m~_M)
                                   = BBS.calculate_random_scalars(M + 2)
2. C = J_1 * msg_1 + ... + J_M * msg_M + Q_2 * secret_prover_blind
3. Cbar = J_1 * m~_1 + ... + J_M * m~_M + Q_2 * s~

4. challenge = calculate_blind_challenge(C, Cbar, blind_generators,
                                                                 api_id)

5. s^ = s~ + secret_prover_blind * challenge
6. for i in (1, 2, ..., M): m^_i = m~_i + msg_i * challenge

7. proof = (s^, (m^_1, ..., m^_M), challenge)
8. commit_with_proof = commitment_with_proof_to_octets(C, proof)
9. return (commit_with_proof, secret_prover_blind)

4.3.2.  Core Commitment Verification

   This operation is used by the Signer to verify the correctness of a
   commitment_proof for a supplied commitment, over a list of points of
   G1 called the blind_generators, used to compute that commitment.

result = CoreCommitVerify(commitment, commitment_proof,
                                               blind_generators, api_id)

Inputs:

- commitment (REQUIRED), a commitment (see (#terminology)).
- commitment_proof (REQUIRED), a commitment_proof (see (#terminology)).
- blind_generators (REQUIRED), vector of pseudo-random points in G1.
- api_id (OPTIONAL), octet string. If not supplied it defaults to the
                     empty octet string ("").

Outputs:

- result: either VALID or INVALID

Deserialization:

1. (s^, commitments, cp) = commitment_proof

2. M = length(commitments)
3. (m^_1, ..., m^_M) = commitments

4. if length(blind_generators) != M, return INVALID
5. (Q_2, J_1, ..., J_M) = blind_generators

Procedure:

1. Cbar = J_1 * m^_1 + ... + J_M * m^_M + Q_2 * s^ + commitment * (-cp)
2. cv = calculate_blind_challenge(commitment, Cbar, blind_generators,
                                                                 api_id)
3. if cv != cp, return INVALID
4. return VALID

4.3.3.  Finalize Blind Sign

   This operation computes a blind BBS signature, from a secret key
   (SK), a set of generators (points of G1), a supplied commitment with
   its proof of correctness (commitment_with_proof), a header (header)
   and a set of messages (messages).  The operation also accepts the
   identifier of the BBS Interface, calling this core operation.

blind_signature = FinalizeBlindSign(SK,
                                    PK,
                                    B,
                                    generators,
                                    blind_generators,
                                    header,
                                    api_id)

Inputs:

- SK (REQUIRED), a secret key in the form outputted by the KeyGen
                 operation.
- PK (REQUIRED), an octet string of the form outputted by SkToPk
                 provided the above SK as input.
- B (REQUIRED), a point of G1, different than Identity_G1.
- generators (REQUIRED), vector of pseudo-random points in G1.
- blind_generators (OPTIONAL), vector of pseudo-random points in G1. If
                               not supplied it defaults to the empty
                               array.
- header (OPTIONAL), an octet string containing context and application
                     specific information. If not supplied, it defaults
                     to an empty string.
- api_id (OPTIONAL), an octet string. If not supplied it defaults to the
                     empty octet string ("").

Outputs:

- blind_signature, a blind signature encoded as an octet string; or
                   INVALID.

Definitions:

1. signature_dst, an octet string representing the domain separation
                  tag: api_id || "H2S_" where "H2S_" is an ASCII string
                  composed of 4 bytes.

Deserialization:

1. L = length(generators) - 1
2. M = length(blind_generators) - 1

3. if L <= 0 or M <=0, return INVALID
4. (Q_1, H_1, ..., H_L) = generators
5. (Q_2, J_1, ..., J_M) = blind_generators

Procedure:

1. domain = BBS.calculate_domain(PK, Q_1, 
                    (H_1, ..., H_L, Q_2, J_1, ..., J_M), header, api_id)
2. e_octs = BBS.serialize((SK, B, domain))
3. e = BBS.hash_to_scalar(e_octs, signature_dst)
4. A = B * (1 / (SK + e))
5. return BBS.signature_to_octets((A, e))

4.3.4.  Core Proof Generation

   The Proof Generation with extension, combines the BBS Proof
   Generation operations (i.e., BBS.ProofInit and BBS.ProofFinalize)
   with a proof of correctness of commitments over some of the signed
   messages.  The commitments proof of correctness will similarly
   constitute of a initialization and finalization phase.  The two proof
   protocols will use a common challenge, returned by the
   ProofChallengeCalculate operation described in Section 5.3.  The
   result of the commitments proof of correctness initialization process
   will be an object of the following form

   CommitInitRes = {
     commits: (REQUIRED) Array of points in G1,
     commits_proofs: (REQUIRED) Array of Scalars,
     indexes: (REQUIRED) Array of numbers
   }

   Following, we describe the Proof Generation Procedure.

proof = CoreProofGen(PK, signature, generators, header, ph, messages,
                             disclosed_indexes, commits_indexes, api_id)

Inputs:

- PK (REQUIRED), an octet string of the form outputted by the SkToPk
                 operation.
- signature (REQUIRED), an octet string of the form outputted by the
                        Sign operation.
- generators (REQUIRED), vector of pseudo-random points in G1.
- header (OPTIONAL), an octet string containing context and application
                     specific information. If not supplied, it defaults
                     to the empty octet string ("").
- ph (OPTIONAL), an octet string containing the presentation_header. If
                 not supplied, it defaults to the empty octet
                 string ("").
- messages (OPTIONAL), a vector of scalars representing the messages.
                       If not supplied, it defaults to the empty
                       array ("()").
- disclosed_indexes (OPTIONAL), vector of non-negative integers in
                                ascending order. Indexes of disclosed
                                messages. If not supplied, it defaults
                                to the empty array ("()").
- commits_indexes (OPTIONAL), vector of non-negative integers in
                              ascending order. Indexes of disclosed
                              messages. If not supplied, it defaults
                              to the empty array ("()").
- api_id (OPTIONAL), an octet string. If not supplied it defaults to the
                     empty octet string ("").

Parameters:

- Y_0 and Y_1, fixed points of G1 computed as
  (Y_0, Y_1) = BBS.create_generators(2, "COM_DIS_" || api_id)

Outputs:

- proof, an octet string; or INVALID.

Deserialization:

1. signature_result = octets_to_signature(signature)
2. if signature_result is INVALID, return INVALID

3*. if commits_indexes is not a list of integers from 0 to L in
    accenting order, return INVALID
4*. if disclosed_indexes is not a list of integers from 0 to L in
    accenting order, return INVALID

Procedure:

1.  init_res = BBS.ProofInit(PK,
                             signature_result,
                             generators,
                             header,
                             messages,
                             disclosed_indexes,
                             api_id)
2.  if init_res is INVALID, return INVALID

// Calculate the commitments and initiate the correctness proof
3.  N = length(commits_indexes)
4.  (s_1, ..., s_N, s~_1, ..., s~_N) = calculate_random_scalars(2*N)
5.  init_random_scalars = init_res.random_scalars

6.  for i in 1...N,
7.      idx = commits_indexes[i]
8.      C_i = Y_0 * s_i + Y_1 * messages[idx]
9.      C~_i = Y_0 * s~_i + Y_1 * init_random_scalars[idx + 5]

10. commit_init_res = {commits: (C_1, ..., C_N),
                       commits_proofs: (C~_1, ...,C~_N)
                       indexes: commits_indexes}

11. challenge = ProofChallengeCalculate(init_res, commit_init_res, 
                                        ph, api_id)
12. if challenge is INVALID, return INVALID

13. bbs_proof = BBS.ProofFinalize(init_res, challenge)

// Finalize the commitment correctness proof
14. for i in 1...N, s^_i =  s~_i + challenge * s_i
15. commits_proof = ((C_1, ..., C_N), (s^_1, ..., s^N))

16. proof = proof_to_octets(length(bbs_proof), bbs_proof,
                           length(disclosed_indexes), disclosed_indexes,
                           length(commits_proof), commits_proof
                           length(commits_indexes), commits_indexes)
17. add_zkp_info = {commits: (C_1, ..., C_N),
                    commit_rands: (s_1, ..., s_N)}
18. return [proof, add_zkp_info]

4.3.5.  Core Proof Verification

result = CoreProofVerify(PK, proof, generators, header, ph,
                                             disclosed_messages, api_id)

Inputs:

- PK (REQUIRED), an octet string of the form outputted by the SkToPk
                 operation.
- bbs_proof (REQUIRED), an array with four octet strings of the form
                    outputted by the ProofGen operation.
- commits_proof (REQUIRED), an tuple consisting from an array of points
                            in G1 and an array of scalars of the same
                            length. Both arrays can be empty.
- generators (REQUIRED), vector of pseudo-random points in G1.
- header (OPTIONAL), an optional octet string containing context and
                     application specific information. If not supplied,
                     it defaults to the empty octet string ("").
- ph (OPTIONAL), an octet string containing the presentation_header. If
                 not supplied, it defaults to the empty octet
                 string ("").
- disclosed_messages (OPTIONAL), a vector of scalars representing the
                                 messages. If not supplied, it defaults
                                 to the empty array ("()").
- api_id (OPTIONAL), an octet string. If not supplied it defaults to the
                     empty octet string ("").

Parameters:

- P1, fixed point of G1, defined by the ciphersuite.

Outputs:

- result, either VALID or INVALID.

Deserialization:

1.  W = octets_to_pubkey(PK)
2.  if W is INVALID, return INVALID

3.  proof_res = octets_to_proof(proof)
4.  if proof_res is INVALID, return INVALID
5.  (bbs_proof_res, disclosed_indexes,
                         commits_proof_res, commits_indexes) = proof_res

6.  (Abar, Bbar, D, e^, r1^, r3^, hats, cp) = bbs_proof_res
7.  (commits, commits_proof) = commits_proof_res

8.  if length(commits) != length(commits_proof) or
          if length(commits) != length(commits_indexes) return INVALID
9.  L = length(generators)

10. if commits_indexes is not a list of integers from 0 to L in
    accenting order, return INVALID

11. if disclosed_indexes is not a list of integers from 0 to L in
    accenting order, return INVALID

12. (C_1, ... C_N) = commits
13. (s^_1, ..., s^_N) = commits_proof

Procedure:

1.  init_res = ProofVerifyInit(PK, proof_result, generators, header,
                                                   disclosed_messages,
                                                   disclosed_indexes,
                                                   api_id)
2.  if init_res is INVALID, return INVALID

3.  for i in 1...length(commits),
4.      idx = commit_indexes[i]
5.      C^_i = Y_0 * s^_i + Y_1 * hats[idx] - C_i * cp

6.  commit_init_res = {commits: (C_1, ..., C_N),
                       commits_proofs: (C^_1, ...,C^_N)
                       indexes: commit_indexes}

7.  challenge = ProofChallengeCalculate(init_res, commit_init_res, 
                                        ph, api_id)
8.  if challenge is INVALID, return INVALID

9.  if cp != challenge, return INVALID
10. if h(Abar, W) * h(Bbar, -BP2) != Identity_GT, return INVALID
11. return VALID

5.  Utilities

5.1.  Calculate B value

 res  = B_calculate(generators, commitment, message_scalars)

 Inputs:

 - generators (REQUIRED), an array of at least one point from the
                          G1 group.
 - commitment (OPTIONAL), a point from the G1 group. If not supplied it
                          defaults to the Identity_G1 point.
 - message_scalars (OPTIONAL), an array of scalar values. If not
                               supplied, it defaults to the empty
                               array ("()").

 Outputs:

 - res, an array of a single element from the G1 subgroup, or INVALID.

 Deserialization:

 1. L = length(message_scalars)
 2. if length(generators) != L + 1, return INVALID
 3. (Q_1, H_1, ..., H_L) = generators
 4. (msg_1, ..., msg_L) = message_scalars

 Procedure:

 1. B = P1 + Q_1 * domain + H_1 * msg_1 + ... + H_L * msg_L + commitment
 2. if B is Identity_G1, return INVALID
 3. return (B)

5.2.  Blind Challenge Calculation

 challenge = calculate_blind_challenge(C, Cbar, generators, api_id)

 Inputs:

 - C (REQUIRED), a point of G1.
 - Cbar (REQUIRED), a point of G1.
 - generators (REQUIRED), an array of points from G1, of length at
                          least 1.
 - api_id (OPTIONAL), octet string. If not supplied it defaults to the
                      empty octet string ("").

 Definition:

 - blind_challenge_dst, an octet string representing the domain
                        separation tag: api_id || "H2S_" where
                        ciphersuite_id is defined by the ciphersuite and
                        "H2S_" is an ASCII string composed of 4 bytes.

 Deserialization:

 1. if length(generators) == 0, return INVALID
 2. M = length(generators) - 1

 Procedure:

 1. c_arr = (M)
 2. c_arr.append(generators)
 3. c_octs = BBS.serialize(c_arr.append(C, Cbar))
 4. return BBS.hash_to_scalar(c_octs, blind_challenge_dst)

5.3.  Proof Challenge Calculation

challenge = ProofChallengeCalculate(bbs_init_res, commit_init_res, 
                                    ph, api_id)

Inputs:
- init_res (REQUIRED), a ProofInitRes object representing the value
                       returned after initializing the proof generation
                       or verification operations.
- commit_init_res (REQUIRED), a CommitInitRes representing the value
                              returned after initializing the commits
                              proof of correctness generation or
                              verification.
- ph (OPTIONAL), an octet string. If not supplied, it must default to
                 the empty octet string ("").
- api_id (OPTIONAL), an octet string. If not supplied it defaults to the
                     empty octet string ("").

Outputs:

- challenge, a scalar.

Definitions:

1. hash_to_scalar_dst, an octet string representing the domain
                       separation tag: api_id || "H2S_" where "H2S_" is
                       an ASCII string comprised of 4 bytes.

Deserialization:

1.  if validate_init_res(init_res) returns INVALID, return INVALID
2.  (Abar, Bbar, D, T1, T2, domain) = (init_res.Abar,
                                       init_res.Bbar,
                                       init_res.D,
                                       init_res.T1,
                                       init_res.T2,
                                       init_res.domain)

3.  R = length(init_res.disclosed_indexes)
4.  (i1, ..., iR) = init_res.disclosed_indexes
5.  (msg_i1, ..., msg_iR) = init_res.disclosed_messages

6.  N = length(commit_init_res.commits)
7.  if length(commit_init_res.commits_proofs) != N, return INVALID
8.  if length(commit_init_res.commits_indexes) != N, return INVALID
9.  (C_1, ..., C_N) = commit_init_res.commits
10. (C~_1, ...,C~_N) = commit_init_res.commits_proofs
11. (i_1, ..., i_N) = commit_init_res.commits_indexes

ABORT if:

1. R > 2^64 - 1
2. length(ph) > 2^64 - 1

Procedure:

1. c_arr = (R, i1, msg_i1, i2, msg_i2, ..., iR, msg_iR, Abar, Bbar,
                                                      D, T1, T2, domain)
2. c_octs = serialize(c_arr)

3. c_octs = c_octs || serialize(N, i_1, C_1, C~_1, ..., i_N, C_N, C~_N)

5. c_octs = c_octs || I2OSP(length(ph), 8) || ph
6. return hash_to_scalar(c_octs, hash_to_scalar_dst)

5.4.  Serialize

5.4.1.  Commitment with Proof to Octets

 commitment_octets = commitment_with_proof_to_octets(commitment, proof)

 Inputs:

 - commitment (REQUIRED), a point of G1.
 - proof (REQUIRED), a vector comprising of a scalar, a possibly empty
                     vector of scalars and another scalar in that order.

 Outputs:

 - commitment_octets, an octet string or INVALID.

 Procedure:

 1. commitment_octs = BBS.serialize((commitment))
 2. if commitment_octs is INVALID, return INVALID
 3. (s^, (m^_1, ..., m^_M), challenge) = proof
 4. proof_octs = BBS.serialize((s^, m^_1, ..., m^_M, challenge))
 5. if proof_octs is INVALID, return INVALID
 6. return commitment_octs || proof_octs

5.4.2.  Octets to Commitment with Proof

commitment = octets_to_commitment_with_proof(commitment_octs)

Inputs:

- commitment_octs (REQUIRED), an octet string in the form outputted from
                              the commitment_to_octets operation.

Parameters:

- (octet_point_length, octet_scalar_length), defined by the ciphersuite.

Outputs:

- commitment, a commitment in the form (C, proof), where C a point of G1
              and a proof vector comprising of a scalar, a possibly
              empty vector of scalars and another scalar in that order.

Procedure:

1.  commit_len_floor = octet_point_length + 2 * octet_scalar_length
2.  if length(commitment_octs) < commit_len_floor, return INVALID

3.  C_octets = commitment_octs[0..(octet_point_length - 1)]
4.  C = octets_to_point_E1(C_octets)
5.  if C is INVALID, return INVALID
6.  if C == Identity_G1, return INVALID

7.  j = 0
8.  index = octet_point_length
9.  while index < length(commitment_octs):
10.     end_index = index + octet_scalar_length - 1
11.     s_j = OS2IP(commitment_octs[index..end_index])
12.     if s_j = 0 or if s_j >= r, return INVALID
13.     index += octet_scalar_length
14.     j += 1

15. if index != length(commitment_octs), return INVALID
16. if j < 2, return INVALID
17. msg_commitments = ()
18. if j >= 3, set msg_commitments = (s_1, ..., s_(j-1))
19. return (C, (s_0, msg_commitments, s_j))

5.4.3.  Proof to Octets

proof_octets = proof_to_octets(bbs_proof_len, bbs_proof,
                              disclosed_indexes_len, disclosed_indexes
                              commits_proof_len, commits_proof,
                              commits_indexes_len, commits_indexes)

Inputs:

- bbs_proof_len (REQUIRED), a non negative integer.
- bbs_proof (REQUIRED), an array comprising from 3 points in G1,
                        3 Scalars, an array of scalars and one
                        additional Scalar at the end.
- disclosed_indexes_len (REQUIRED), a non negative integer.
- disclosed_indexes (REQUIRED), an array of non negative integers.
- commits_proof_len (REQUIRED), a non negative integer.
- commits_proof (REQUIRED), a tuple with two arrays, the first with
                            points in G1 and the second with Scalars
- commits_indexes_len (REQUIRED), a non negative integer.
- commits_indexes  (REQUIRED), an array of non negative integers.

Outputs:

- proof_octets, an octet string.

Procedure:

1. oct = I2OSP(bbs_proof_len) || serialize(bbs_proof) ||
         I2OSP(disclosed_indexes_len) || serialize(disclosed_indexes) ||
         I2OSP(commits_proof_len) || serialize(commits_proof) ||
         I2OSP(commits_indexes_len) || serialize(commits_indexes)
2. return oct

5.4.4.  Octets to Proof

   The octets_to_proof procedure, on input an octet string will return a
   BBS with commits proof comprised from the following elements

   1.  A BBS Proof
   2.  The indexes that the disclosed messages have in the list of
       signed messages (both known to the issuer and known only to the
       prover)
   3.  A tuple with two arrays.  One array of points in G1,
       corresponding to the message commits and one array with scalars,
       corresponding to the proof of correctness of the previous
       commitments.
   4.  The indexes that the committed messages have in the list of
       signed messages (both known to the issuer and known only to the
       prover)

proof = octets_to_proof(proof_octets)

- proof_octets (REQUIRED), an octet string of the form outputted from
                           the proof_to_octets operation.

Parameters:

- int_octet_length = 8. The number of octets of encoded integers.
- r, non-negative integer. The prime order of the G1 and G2 groups,
      defined by the ciphersuite.
- octet_scalar_length, non-negative integer. The length of a scalar
                       octet representation, defined by the ciphersuite.
- octet_point_length, non-negative integer. The length of a point in G1
                      octet representation, defined by the ciphersuite.
- subgroup_check_G1, operation that on input a point P returns VALID if
                     P is a valid point of the G1 subgroup, otherwise it
                     returns INVALID (see (#notation)).

Outputs:

- proof, a proof value in the form described above or INVALID

Procedure:

1.  sidx = 0
2.  eidx = int_octet_length - 1
3.  if length(proof_octets) < eidx, return INVALID
4.  bbs_proof_len = OS2IP(proof_octets[sidx..eidx])

5.  sidx = eidx + 1
6.  eidx = sidx + bbs_proof_len
7.  if length(proof_octets) < eidx, return INVALID
8.  bbs_proof_octs = proof_octets[sidx..eidx]
9.  bbs_proof = BBS.octets_to_proof(bbs_proof_octs)
10. if bbs_proof is INVALID, return INVALID

// Deserialize disclosed_indexes
11. sidx = eidx + 1
12. eidx = sidx + int_octet_length
13. if length(proof_octets) < eidx, return INVALID
14. U = OS2IP(proof_octets[sidx..eidx]) // disclosed_indexes len
15. if length(proof_octets) < eidx + U * int_octet_length,
                                                          return INVALID

16. for i in (1...U)
17.     sidx = eidx + 1
18.     eidx = sidx + int_octet_length
19.     idx_i = OS2IP(proof_octets[sidx..eidx])

20. disclosed_indexes = (idx_1, ..., idx_U)

// Deserialize commits_proof
21. sidx = eidx + 1
22. eidx = sidx + int_octet_length
23. if length(proof_octets) < eidx, return INVALID
24. N = OS2IP(proof_octets[sidx..eidx]) // commits_proof len

25. len_floor = eidx + N * (octet_point_length + octet_scalar_length)
26. if length(proof_octets) < len_floor, return INVALID

27. for i in (1..N)
28.     sidx = eidx + 1
29.     eidx = sidx + octet_point_length
30.     C_i = BBS.octets_to_point_E1(proof_octets[sidx..eidx])
31.     if C_i is INVALID or Identity_G1, return INVALID
32.     if subgroup_check_G1(C_i) returns INVALID, return INVALID

33. for i in (1..N)
34.     sidx = eidx + 1
35.     eidx + octet_scalar_length
36.     s_i = OS2IP(proof_octetss[sidx..eidx])
37.     if s_i = 0 or if s_i >= r, return INVALID

38. commits_proof = ((C_1, ..., C_N), (s_1, ..., s_N))

// Desirialize commits_indexes
39. sidx = eidx + 1
40. eidx = sidx + int_octet_length
41. if length(proof_octets) < eidx, return INVALID
42. N' = OS2IP(proof_octets[sidx..eidx]) // commits_indexes len

43. if length(proof_octets) < eidx + N' * int_octet_length,
                                                          return INVALID
44. for i in (1...N')
45.     sidx = eidx + 1
46.     eidx = sidx + int_octet_length
47.     cidx_i = OS2IP(proof_octets[sidx..eidx])

48. commits_indexes = (cidx_1, ..., cidx_N')
49. if N' not equal to N, return INVALID
50. if length(proof) not equal to eidx, return INVALID
51. return (bbs_proof, disclosed_indexes,
            commits_proof, commits_indexes)

6.  Privacy Considerations

   The privacy considerations discussed in Section 5
   (https://www.ietf.org/archive/id/draft-irtf-cfrg-bbs-signatures-
   09.html#name-privacy-considerations) of
   [I-D.irtf-cfrg-bbs-signatures] apply to this draft as well.

6.1.  Total Number and Index of Committed Messages

   When a Prover submits a commitment to the Signer, the Prover's
   committed messages are "perfectly" (statistically) hidden from the
   Signer.  However, the proof of the committed messages, which is also
   sent from the Prover to the Signer, contains the number of committed
   messages.

   In the proof sent from the Prover to the Verifier the number of
   committed messages can be inferred.  In addition, indexes of
   disclosed committed messages are revealed to the Verifier.  In
   Section 5.2 (https://www.ietf.org/archive/id/draft-irtf-cfrg-bbs-
   signatures-09.html#name-total-number-and-index-of-s) of
   [I-D.irtf-cfrg-bbs-signatures] the threats to unlinkability and
   mitigations for this information with respect to Signer messages is
   discussed.  These threats and mitigations apply to the Prover total
   number of committed messages and the disclosed committed indexes as
   well.

7.  Application Considerations

7.1.  Input Validity Checks

   Applications using CoreProofGen (as defined in Section 4.3.4) only as
   a subroutine of BlindProofGen (as defined in Section 4.2.3), can skip
   the checks of the commits_indexes and disclosed_indexes inputs,
   performed at step 3* and 4* of the Deserialization section of that
   operation, since the inputs provided by the calling operation (i.e.,
   BlindProofGen) will always have the correct form.  However, if
   applications intend to use CoreProofGen in different contexts (and
   not necesearily only call it from BlindProofGen), those checks must
   be applied.

8.  Security Considerations

   Security considerations detailed in Section 6
   (https://www.ietf.org/archive/id/draft-irtf-cfrg-bbs-signatures-
   09.html#name-security-considerations) of
   [I-D.irtf-cfrg-bbs-signatures] apply to this draft as well.

8.1.  Prover Blind Factor

   The random scalar value secret_prover_blind calculated and returned
   by the Commit operation is responsible for "hiding" the committed
   messages (otherwise, in many practical applications, the Signer may
   be able to retrieve them).  Furthermore, it guarantees that the
   entity generating the BBS proof (see BlindProofGen defined in
   Section 4.2.3) has knowledge of that factor.  As a result, the
   secret_prover_blind MUST remain private by the Prover and it MUST be
   generated using a cryptographically secure pseudo-random number
   generator.  See Section 6.7 (https://www.ietf.org/archive/id/draft-
   irtf-cfrg-bbs-signatures-09.html#name-randomness-requirements) of
   [I-D.irtf-cfrg-bbs-signatures] on recommendations and requirements
   for implementing the BBS.calculate_random_scalars operation (which is
   used to calculate the secret_prover_blind value).

8.2.  Key Binding

   One natural use case for the blind signatures extension of the BBS
   scheme is key binding.  In the context of BBS Signatures, key binding
   guarantees that only entities in control of a specific private key
   can compute BBS proofs.  This can be achieved by committing to the
   private key prior to issuance, resulting in a BBS signature that
   includes that key as one of the signed messages.  Creating a BBS
   proof from that signature will then require knowledge of that key
   (similar to any signed message).  The Prover MUST NOT disclose that
   key as part of a proof generation procedure.  Note also that the
   secret_prover_blind value returned by the Commit operation defined in
   Section 4.1.1 (see Section 8.1), has a similar property, i.e., it's
   knowledge is required to generate a proof from a blind signature.
   Many applications however, requiring key binding, mandate that the
   same private key is used among multiple signatures, whereas the
   secret_prover_blind is uniquely generated for each blind signature
   issuance request.  In those cases, a commitment to a private key must
   be used, as described above.

9.  Ciphersuites

   This document uses the BBS_BLS12381G1_XOF:SHAKE-256_SSWU_RO_ and
   BBS_BLS12381G1_XMD:SHA-256_SSWU_RO_ defined in Section 7.2.1
   (https://identity.foundation/bbs-signature/draft-irtf-cfrg-bbs-
   signatures.html#name-bls12-381-shake-256) and Section 7.2.2
   (https://identity.foundation/bbs-signature/draft-irtf-cfrg-bbs-
   signatures.html#name-bls12-381-sha-256) correspondingly, of
   [I-D.irtf-cfrg-bbs-signatures].

10.  Test Vectors

   Test vectors are being revised to include new committed disclosure
   functionality.

11.  IANA Considerations

   This document does not make any requests of IANA.

12.  Normative References

   [I-D.irtf-cfrg-bbs-signatures]
              Looker, T., Kalos, V., Whitehead, A., and M. Lodder, "The
              BBS Signature Scheme", Work in Progress, Internet-Draft,
              draft-irtf-cfrg-bbs-signatures-10, 8 January 2026,
              <https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-
              bbs-signatures-10>.

   [RFC2119]  Bradner, S., "Key words for use in RFCs to Indicate
              Requirement Levels", BCP 14, RFC 2119,
              DOI 10.17487/RFC2119, March 1997,
              <https://www.rfc-editor.org/info/rfc2119>.

   [RFC8174]  Leiba, B., "Ambiguity of Uppercase vs Lowercase in RFC
              2119 Key Words", BCP 14, RFC 8174, DOI 10.17487/RFC8174,
              May 2017, <https://www.rfc-editor.org/info/rfc8174>.

   [RFC9474]  Denis, F., Jacobs, F., and C. A. Wood, "RSA Blind
              Signatures", RFC 9474, DOI 10.17487/RFC9474, October 2023,
              <https://www.rfc-editor.org/info/rfc9474>.

13.  Informative References

   [BG18]     Bootle, J. and J. Groth, "Efficient Batch Zero-Knowledge
              Arguments for Low Degree Polynomials", In CRYPTO, 2018,
              <https://link.springer.com/
              chapter/10.1007/978-3-319-76581-5_19>.

   [Chaum85]  Chaum, D., "Security without identification: transaction
              systems to make big brother obsolete", In Commun. ACM,
              vol 10, pages 1030-1044, 1985,
              <https://dl.acm.org/doi/pdf/10.1145/4372.4373>.

   [I-D.ietf-privacypass-protocol]
              Celi, S., Davidson, A., Valdez, S., and C. A. Wood,
              "Privacy Pass Issuance Protocol", Work in Progress,
              Internet-Draft, draft-ietf-privacypass-protocol-16, 3
              October 2023, <https://datatracker.ietf.org/doc/html/
              draft-ietf-privacypass-protocol-16>.

   [LegacyBinding2026]
              Celi, S., Lehmann, A., Levin, S., and A. Zacharakis,
              "Device Binding for Anonymous Credentials on Legacy
              Phones", 2026, <https://eprint.iacr.org/2026/965>.

   [P91]      Pedersen, T., "Non-Interactive and Information-Theoretic
              Secure Verifiable Secret Sharing", In CRYPTO, 1991,
              <https://ia.cr/2023/275>.

   [Vision2025]
              Lehmann, A., Sidorenko, A., and A. Zacharakis, "Vision: A
              Modular Framework for Anonymous Credential Systems", 2025,
              <https://eprint.iacr.org/2025/1981>.

Appendix A.  Document History

   -00

   *  Initial Version

   -01

   *  Change committed_messages to committed_scalars in CoreCommit
   *  Added explanatory text
   *  Added test vectors

   -02

   *  Expanded privacy and security considerations
   *  Updated the introduction

   -03

   *  Add committed disclosure functionality and explanatory text
   *  Editorial fixes

Authors' Addresses

   Vasilis Kalos
   MATTR
   Email: vasilis.kalos@mattr.global

   Greg M. Bernstein
   Grotto Networking
   Email: gregb@grotto-networking.com