Skip to main content

PulseProof Sentinel Protocol Specification
draft-hezami-pulseproof-sentinel-00

Document Type Active Internet-Draft (individual)
Author Hossein Hezami
Last updated 2026-07-23 (Latest revision 2026-07-22)
RFC stream (None)
Intended RFC status (None)
Formats
Additional resources Author GitHub profile: hosseinhezami
Core PHP implementation
Core PHP package on Packagist
Documentation site
Laravel integration package
Laravel package on Packagist
Main specification repository
Project GitHub organization
Project website
Protocol specification
Stream Stream state (No stream defined)
Consensus boilerplate Unknown
RFC Editor Note (None)
IESG IESG state I-D Exists
Telechat date (None)
Responsible AD (None)
Send notices to (None)
draft-hezami-pulseproof-sentinel-00
Network Working Group                                      H. Hezami
Internet-Draft
Intended status: Informational                          23 July 2026
Expires: 23 January 2027

          PulseProof Sentinel Protocol Specification
          draft-hezami-pulseproof-sentinel-00

Abstract

   This document specifies the PulseProof Sentinel Protocol (PPS), an
   experimental, technology-agnostic protocol for time-bound asymmetric
   authentication proofs.  PPS is not a new cryptographic primitive; it
   profiles established mechanisms -- Ed25519 signatures, SHA-256
   hashing, HKDF key derivation, and deterministic CBOR encoding --
   into a compact, interoperable verification model suited to offline,
   embedded, and non-browser environments where a full WebAuthn
   ceremony is unavailable.

   A PPS authentication event produces a Pulse: a signed statement
   bound to a relying-party identifier, time epoch, monotonic counter,
   expiration, and optional nonce, context, policy, and transaction
   data.  Because the verifier stores only public keys, PPS removes the
   shared-secret exposure problem inherent in TOTP.

   PulseProof Sentinel extends the basic Pulse model with several
   optional but interoperable security extensions:

   1.  Verifiable Asymmetric Forward-Secure Ratcheting
   2.  Silent Duress and Honey-Pulse signaling
   3.  Offline threshold co-signing
   4.  Environmental and proximity context binding
   5.  Dynamic policy negotiation
   6.  Human-verifiable Trust Codes with transaction AmountMark
       embedding

   This specification defines the wire format, cryptographic profiles,
   verification rules, security model, and conformance requirements
   for PPS version 1.

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 23 January 2027.

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 and Requirements Language
   2.  Terminology
     2.1.  Relying Party
     2.2.  Authenticator
     2.3.  Pulse
     2.4.  Pulse Token
     2.5.  Rotation Certificate
     2.6.  Duress Key
     2.7.  Honey-Pulse
     2.8.  Threshold Group
     2.9.  Trust Code
     2.10. AmountMark
   3.  Design Goals
   4.  Non-Goals
   5.  Notation and Encoding
     5.1.  Byte Order
     5.2.  Base64url
     5.3.  CBOR
     5.4.  Hashing
     5.5.  Key-derivation
     5.6.  Randomness
   6.  Cryptographic Suites
     6.1.  Mandatory Suite
     6.2.  Optional Suites
   7.  Time Model
     7.1.  Unix Time
     7.2.  Epoch
     7.3.  Clock Skew
     7.4.  Expiration
   8.  Identifiers
     8.1.  Relying Party Identifier
     8.2.  Key Identifier
     8.3.  Group Identifier
     8.4.  User Handle
   9.  Data Structures
     9.1.  Message Types
     9.2.  Registration Payload
     9.3.  Registration Envelope
     9.4.  Rotation Payload
     9.5.  Rotation Certificate
     9.6.  Pulse Payload
     9.7.  Pulse Envelope
     9.8.  Context Object
     9.9.  Policy Object
   10. Pulse Token Encoding
   11. Registration
     11.1. Registration Flow
     11.2. Registration Verification
     11.3. Initial Server Key State
   12. Verifiable Asymmetric Forward-Secure Ratchet
     12.1. Ratchet Principle
     12.2. Rotation Sequence
     12.3. Rotation Certificates
     12.4. Secure Deletion
   13. Pulse Generation
     13.1. Inputs
     13.2. Single-Device Pulse Generation
     13.3. Duress Pulse Generation
     13.4. Threshold Pulse Generation
   14. Pulse Verification
     14.1. Token Parsing
     14.2. Common Payload Checks
     14.3. Single-Device Verification
     14.4. Duress Verification
     14.5. Threshold Verification
   15. Trust Code
     15.1. Trust Input
     15.2. Trust Salt
     15.3. Trust OKM
     15.4. Numeric Trust Code Length
     15.5. Unbiased Decimal Derivation
     15.6. AmountMark Embedding
     15.7. Trust Code Verification
   16. Trust Phrase
   17. Context Binding
     17.1. Context Hash
     17.2. Recommended Context Fields
     17.3. Proximity Example
   18. Policy Binding
     18.1. Policy Hash
     18.2. Policy Verification
     18.3. Downgrade Protection
   19. Transaction Binding
   20. Nonce and Replay Protection
     20.1. Online Mode
     20.2. Offline Mode
     20.3. Counter
   21. Error Handling
   22. Optional URI Scheme
     22.1. Challenge URI
   23. HTTP Binding
     23.1. Challenge Response
     23.2. Verification Request
     23.3. Verification Response
   24. Conformance
     24.1. Core Authenticator
     24.2. Core Verifier
     24.3. Forward-Secure Profile
     24.4. Duress Profile
     24.5. Threshold Profile
   25. Security Considerations
     25.1. Shared Secret Removal
     25.2. Phishing Resistance
     25.3. Trust Code Entropy
     25.4. Replay Protection
     25.5. Forward Secrecy Limitations
     25.6. Rotation Certificate Abuse
     25.7. Duress Safety
     25.8. Threshold Mode
     25.9. Malware and User Approval
     25.10. Clock Skew
     25.11. CBOR Parsing
     25.12. Post-Quantum Considerations
   26. Privacy Considerations
     26.1. Minimal Personal Data
     26.2. Pairwise Identifiers
     26.3. Context Privacy
     26.4. Duress Privacy
   27. IANA Considerations
     27.1. Media Type Registration
     27.2. URI Scheme Registration
     27.3. Algorithm Registry
   28. Normative References
   29. Informative References
   Appendix A.  CDDL Summary
   Appendix B.  Recommended Server State
   Appendix C.  Recommended Verification Order
   Appendix D.  Patent and Implementation Notice
   Author's Address

1.  Introduction and Requirements Language

   The keywords "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.

2.  Terminology

2.1.  Relying Party

   The Relying Party, abbreviated RP, is the service verifying
   authentication.

   Examples:

      example.com
      login.example.com:8443
      android:com.example.app
      ios:com.example.app
      urn:pps:service:internal:billing

2.2.  Authenticator

   The Authenticator is the user-controlled device or software module
   that holds private key material and generates Pulses.

   Examples:

   o  mobile application
   o  desktop application
   o  browser extension
   o  hardware security key
   o  secure enclave-backed device
   o  watch or secondary co-signing device

2.3.  Pulse

   A Pulse is a signed authentication statement generated by an
   Authenticator.

   A Pulse asserts possession of a private key bound to a specific RP,
   time window, counter, and optional session/context/policy/
   transaction data.

2.4.  Pulse Token

   A Pulse Token is the serialized form of a Pulse envelope.

   The canonical binary encoding is deterministic CBOR.  The textual
   encoding is base64url without padding.

2.5.  Rotation Certificate

   A Rotation Certificate is a signed statement authorizing transition
   from one public verification key to the next public verification
   key.

   Rotation certificates enable asynchronous or offline forward-secure
   key ratcheting.

2.6.  Duress Key

   A Duress Key is a separate private key that can be used when the
   user is coerced.

   A Pulse signed by a Duress Key is called a Honey-Pulse.

2.7.  Honey-Pulse

   A Honey-Pulse is cryptographically valid but indicates that the
   user may be under duress.

   A verifier MUST NOT reveal duress detection to the client in the
   outward authentication response.

2.8.  Threshold Group

   A Threshold Group is a set of registered signer keys where a
   minimum number of signatures is required to authorize a Pulse.

2.9.  Trust Code

   A Trust Code is a short human-verifiable numeric value derived from
   the cryptographic proof.

   The Trust Code is not the primary authentication secret.

2.10.  AmountMark

   An AmountMark is a two-digit transaction checksum embedded in a
   Trust Code.

3.  Design Goals

   PulseProof Sentinel is designed to provide:

   1.   No shared secret stored on the verifier.
   2.   Strong cryptographic authentication using digital signatures.
   3.   Replay resistance using nonce, epoch, expiration, and
        monotonic counters.
   4.   Service binding using an RP identifier hash.
   5.   Session binding using a server-issued nonce.
   6.   Context binding using optional environmental or transaction
        data.
   7.   Policy binding using a negotiated security policy hash.
   8.   Forward-secure key evolution using verifiable asymmetric
        ratcheting.
   9.   Silent duress signaling using indistinguishable Honey-Pulses.
   10.  Offline threshold approval using multiple authenticator
        devices.
   11.  Human-verifiable Trust Codes with optional transaction
        checksums.
   12.  Implementation feasibility across common programming
        languages.

4.  Non-Goals

   PulseProof Sentinel does not define:

   o  a full identity federation protocol
   o  a replacement for OAuth 2.0 or OpenID Connect
   o  a general-purpose end-to-end encryption protocol
   o  a blockchain consensus protocol
   o  a device attestation standard
   o  a biometric template storage format

   PPS may be combined with such systems but does not standardize
   them.

5.  Notation and Encoding

5.1.  Byte Order

   All multi-byte unsigned integers explicitly serialized by this
   specification use big-endian byte order when raw integer bytes are
   required.

   CBOR integer encoding follows RFC 8949 [RFC8949].

5.2.  Base64url

   Textual representations of binary values use base64url encoding
   without padding as defined in Section 5 of [RFC4648].

      base64url(binary) = base64url-no-pad(binary)

5.3.  CBOR

   All signed structures defined by this specification are encoded
   using deterministic CBOR.

   Implementations MUST follow the deterministic encoding rules
   described in Section 4.2 of [RFC8949].

   Additional requirements:

   1.  Map keys MUST be sorted in ascending numeric order.
   2.  Indefinite-length items MUST NOT be used.
   3.  Duplicate map keys MUST be rejected.
   4.  Non-minimal integer encodings MUST be rejected.
   5.  Floating-point values MUST NOT appear in signed structures.
   6.  Undefined values MUST NOT appear in signed structures.
   7.  Optional absent fields MUST be omitted.
   8.  Tags MUST NOT be used unless explicitly defined by a future
       version.

5.4.  Hashing

   The mandatory hash algorithm is SHA-256 [FIPS180-4].

   All hash outputs used in PPS fields are raw byte strings.

5.5.  Key-derivation

   The mandatory key-derivation function is HKDF-SHA256 [RFC5869].

5.6.  Randomness

   All random values MUST be generated using a cryptographically
   secure pseudorandom number generator suitable for key generation
   and nonce generation [RFC4086].

   Nonces SHOULD be at least 128 bits and MAY be 256 bits.

6.  Cryptographic Suites

   PPS uses algorithm identifiers to allow cryptographic agility.

6.1.  Mandatory Suite

   The mandatory-to-implement suite for PPS version 1 is:

      PPS-ED25519-CBOR30

   This suite specifies:

   +----------------------+----------------------------------+
   | Property             | Value                            |
   +----------------------+----------------------------------+
   | Signature algorithm  | Ed25519 [RFC8032]                |
   | Public key length    | 32 octets                        |
   | Signature length     | 64 octets                        |
   | Hash algorithm       | SHA-256                          |
   | KDF                  | HKDF-SHA256                      |
   | Default epoch length | 30 seconds                       |
   | Canonical encoding   | deterministic CBOR               |
   | Text encoding        | base64url without padding        |
   +----------------------+----------------------------------+

6.2.  Optional Suites

   The following suites are reserved for future or experimental use:

      PPS-MLDSA44-CBOR30
      PPS-MLDSA65-CBOR30
      PPS-HYB-ED25519-MLDSA44-CBOR30

   These suites reference ML-DSA [FIPS204].

   Support for optional suites is OPTIONAL.

   A verifier MAY reject unknown or disallowed algorithms.

7.  Time Model

7.1.  Unix Time

   PPS uses Unix time, defined as the number of seconds since
   1970-01-01T00:00:00Z, ignoring leap seconds.

7.2.  Epoch

   The default epoch length is 30 seconds.

      epoch = floor(unix_time / 30)

   The epoch length is fixed by the algorithm identifier.

   For PPS-ED25519-CBOR30, the epoch length is 30 seconds.

7.3.  Clock Skew

   Verifiers SHOULD accept Pulses whose epoch is within one epoch of
   the verifier's current epoch:

      current_epoch - 1 <= pulse_epoch <= current_epoch + 1

   Verifiers MAY permit a larger skew through local policy, but larger
   skew increases replay risk.

7.4.  Expiration

   The exp field is an epoch number, not a Unix timestamp.

   A Pulse MUST be rejected if:

      current_epoch > exp

   The RECOMMENDED default is:

      exp = epoch + 2

8.  Identifiers

8.1.  Relying Party Identifier

   The RP identifier, rp_id, is a canonical ASCII string.

   For web origins:

      rp_id = lowercase(host) [ ":" port ]

   The RP identifier MUST NOT include:

   o  scheme
   o  path
   o  query
   o  fragment
   o  userinfo

   Examples:

      example.com
      login.example.com
      example.com:8443

   For application origins:

      android:com.example.app
      ios:com.example.app

   For internal services:

      urn:pps:service:internal:billing

   The RP hash stored in signed structures is:

      rp_hash = SHA256(utf8(rp_id))

8.2.  Key Identifier

   A key identifier, kid, is a byte string of length 16 to 64 octets.

   It SHOULD be randomly generated and stable for a registered device.

   It SHOULD NOT contain personally identifiable information.

8.3.  Group Identifier

   A group identifier, gid, is a byte string of length 16 to 64
   octets.

   It identifies a threshold group.

8.4.  User Handle

   A user handle is an opaque byte string of length 16 to 64 octets.

   It SHOULD NOT contain personally identifiable information unless
   required by the deployment.

9.  Data Structures

   All structures in this section are CBOR maps with integer keys.

9.1.  Message Types

      1 = registration
      2 = pulse
      3 = rotation

9.2.  Registration Payload

      registration-payload = {
        1: version,
        2: type,
        3: alg,
        4: kid,
        5: rp_hash,
        6: pk,
        7: user_handle,
        8: nonce,
        9: created_at,
        ? 10: device_name,
        ? 11: attestation,
        ? 12: duress_pk
      }

   Fields:

   +-----+-------------+------+-------------+---------------------+
   | Key | Name        | Type | Required    | Description         |
   +-----+-------------+------+-------------+---------------------+
   | 1   | version     | uint | YES         | MUST be 1           |
   | 2   | type        | uint | YES         | MUST be 1           |
   | 3   | alg         | tstr | YES         | algorithm ident.    |
   | 4   | kid         | bstr | YES         | device key ident.   |
   | 5   | rp_hash     | bstr | YES         | SHA256(rp_id)       |
   | 6   | pk          | bstr | YES         | normal public key   |
   | 7   | user_handle | bstr | YES         | opaque user ident.  |
   | 8   | nonce       | bstr | YES         | reg. challenge      |
   | 9   | created_at  | uint | YES         | Unix timestamp      |
   | 10  | device_name | tstr | NO          | human-readable name |
   | 11  | attestation | bstr | NO          | platform attest.    |
   | 12  | duress_pk   | bstr | NO          | duress public key   |
   +-----+-------------+------+-------------+---------------------+

   If duress_pk is present, the registration envelope MUST include a
   duress signature.

9.3.  Registration Envelope

      registration-envelope = {
        1: message,
        2: signature,
        ? 3: duress_signature
      }

   Fields:

   +-----+------------------+------+-------------+----------------+
   | Key | Name             | Type | Required    | Description    |
   +-----+------------------+------+-------------+----------------+
   | 1   | message          | bstr | YES         | CBOR-encoded   |
   |     |                  |      |             | reg-payload    |
   | 2   | signature        | bstr | YES         | signature by pk|
   | 3   | duress_signature | bstr | CONDITIONAL | sig by duress  |
   |     |                  |      |             | _pk if present |
   +-----+------------------+------+-------------+----------------+

   The signature is computed over the raw bytes of message.

      signature = Ed25519_sign(sk, message)

   If duress_pk is present:

      duress_signature = Ed25519_sign(duress_sk, message)

9.4.  Rotation Payload

      rotation-payload = {
        1: version,
        2: type,
        3: from_pk,
        4: to_pk,
        5: seq,
        6: epoch,
        7: exp,
        ? 8: rp_hash,
        ? 9: nonce
      }

   Fields:

   +-----+---------+------+----------+---------------------------+
   | Key | Name    | Type | Required | Description               |
   +-----+---------+------+----------+---------------------------+
   | 1   | version | uint | YES      | MUST be 1                 |
   | 2   | type    | uint | YES      | MUST be 3                 |
   | 3   | from_pk | bstr | YES      | current public key        |
   | 4   | to_pk   | bstr | YES      | next public key           |
   | 5   | seq     | uint | YES      | rotation sequence number  |
   | 6   | epoch   | uint | YES      | creation epoch            |
   | 7   | exp     | uint | YES      | expiration epoch          |
   | 8   | rp_hash | bstr | OPTIONAL | SHA256(rp_id)             |
   | 9   | nonce   | bstr | OPTIONAL | server nonce              |
   +-----+---------+------+----------+---------------------------+

9.5.  Rotation Certificate

      rotation-certificate = {
        1: message,
        2: signature
      }

   Fields:

   +-----+-----------+------+----------+------------------------+
   | Key | Name      | Type | Required | Description            |
   +-----+-----------+------+----------+------------------------+
   | 1   | message   | bstr | YES      | CBOR-encoded           |
   |     |           |      |          | rotation-payload       |
   | 2   | signature | bstr | YES      | signature by from_pk   |
   +-----+-----------+------+----------+------------------------+

   The signature is computed over the raw bytes of message.

      signature = Ed25519_sign(from_sk, message)

9.6.  Pulse Payload

      pulse-payload = {
        1: version,
        2: type,
        3: alg,
        5: rp_hash,
        6: epoch,
        7: ctr,
        8: exp,
        ? 11: nonce,
        ? 12: ctx,
        ? 13: pol,
        ? 14: amt,
        (single-mode-fields // threshold-mode-fields)
      }

   Common fields:

   +-----+---------+------+-------------+-----------------------+
   | Key | Name    | Type | Required    | Description           |
   +-----+---------+------+-------------+-----------------------+
   | 1   | version | uint | YES         | MUST be 1             |
   | 2   | type    | uint | YES         | MUST be 2             |
   | 3   | alg     | tstr | YES         | algorithm identifier  |
   | 5   | rp_hash | bstr | YES         | SHA256(rp_id), 32 oct |
   | 6   | epoch   | uint | YES         | creation epoch        |
   | 7   | ctr     | uint | YES         | operation counter     |
   | 8   | exp     | uint | YES         | expiration epoch      |
   | 11  | nonce   | bstr | CONDITIONAL | server nonce          |
   | 12  | ctx     | bstr | OPTIONAL    | SHA256(context-object)|
   | 13  | pol     | bstr | OPTIONAL    | SHA256(policy-object) |
   | 14  | amt     | uint | OPTIONAL    | AmountMark, 0..99     |
   +-----+---------+------+-------------+-----------------------+

9.6.1.  Single-Device Ratchet Mode Fields

      single-mode-fields = (
        4: kid,
        9: pk,
        10: next_pk,
        15: rot_seq
      )

   Fields:

   +-----+---------+------+----------+-------------------------+
   | Key | Name    | Type | Required | Description             |
   +-----+---------+------+----------+-------------------------+
   | 4   | kid     | bstr | YES      | device key identifier   |
   | 9   | pk      | bstr | YES      | current signing pub key |
   | 10  | next_pk | bstr | YES      | next public key         |
   | 15  | rot_seq | uint | YES      | rotation sequence num   |
   +-----+---------+------+----------+-------------------------+

   In single-device ratchet mode:

   o  pk MUST be present.
   o  next_pk MUST be present.
   o  rot_seq MUST be present.
   o  gid and thr MUST be absent.

9.6.2.  Threshold Mode Fields

      threshold-mode-fields = (
        20: gid,
        21: thr
      )

   Fields:

   +-----+------+----------+---------------------------+
   | Key | Name | Type     | Description               |
   +-----+------+----------+---------------------------+
   | 20  | gid  | bstr     | threshold group ident.    |
   | 21  | thr  | uint     | required sig threshold    |
   +-----+------+----------+---------------------------+

   In threshold mode:

   o  gid MUST be present.
   o  thr MUST be present.
   o  kid, pk, next_pk, and rot_seq MUST be absent.
   o  Signatures are collected in the envelope signature map.

9.7.  Pulse Envelope

      pulse-envelope = {
        1: message,
        2: signatures,
        ? 3: certificates
      }

   Fields:

   +-----+--------------+-------+----------+--------------------+
   | Key | Name         | Type  | Required | Description        |
   +-----+--------------+-------+----------+--------------------+
   | 1   | message      | bstr  | YES      | CBOR-encoded       |
   |     |              |       |          | pulse-payload      |
   | 2   | signatures   | map   | YES      | map of signer kid  |
   |     |              |       |          | to signature       |
   | 3   | certificates | array | OPTIONAL | rotation certs     |
   +-----+--------------+-------+----------+--------------------+

   The signature map is:

      signatures = {
        * bstr => bstr
      }

   The map key is the signer kid.

   The map value is the signature over message.

   In single-device ratchet mode, the signature map MUST contain
   exactly one entry whose key equals the Pulse payload kid.

   In threshold mode, the signature map MUST contain at least thr
   valid signatures from registered group members.

9.8.  Context Object

   The context object is OPTIONAL.

      context-object = {
        ? 1: session_id,
        ? 2: tx_hash,
        ? 3: ble_hash,
        ? 4: acoustic_hash,
        ? 5: ip_hash,
        ? 6: ua_hash,
        ? 7: device_integrity_hash,
        ? 8: custom_hash
      }

   All values are byte strings.

   The context hash included in a Pulse is:

      ctx = SHA256(cbor(context-object))

   If no context is used, the ctx field MUST be omitted.

9.9.  Policy Object

   The policy object is OPTIONAL.

      policy-object = {
        ? 1: min_trust_digits,
        ? 2: require_biometric,
        ? 3: require_secure_enclave,
        ? 4: threshold,
        ? 5: max_amount_minor,
        ? 6: require_proximity,
        ? 7: max_clock_skew,
        ? 8: allow_offline,
        ? 9: require_amount_mark,
        ? 10: require_duress_support
      }

   Types:

   +-----+--------------------------+------+
   | Key | Name                     | Type |
   +-----+--------------------------+------+
   | 1   | min_trust_digits         | uint |
   | 2   | require_biometric        | bool |
   | 3   | require_secure_enclave   | bool |
   | 4   | threshold                | uint |
   | 5   | max_amount_minor         | uint |
   | 6   | require_proximity        | bool |
   | 7   | max_clock_skew           | uint |
   | 8   | allow_offline            | bool |
   | 9   | require_amount_mark      | bool |
   | 10  | require_duress_support   | bool |
   +-----+--------------------------+------+

   The policy hash included in a Pulse is:

      pol = SHA256(cbor(policy-object))

   If no policy is used, the pol field MUST be omitted.

10.  Pulse Token Encoding

   The binary Pulse Token is:

      pulse_token_binary = cbor(pulse-envelope)

   The textual Pulse Token is:

      pulse_token = base64url(pulse_token_binary)

   The RECOMMENDED media type is:

      application/pulseproof+cbor

   When transmitted in JSON, binary fields SHOULD be represented as
   base64url strings.

11.  Registration

11.1.  Registration Flow

   Registration binds a device key to a user account at an RP.

   The RP SHOULD issue a fresh registration nonce.

   The Authenticator constructs a registration payload and signs it.

   If a duress key is registered, the Authenticator MUST also sign the
   same registration message with the duress private key.

11.2.  Registration Verification

   The verifier MUST perform the following checks:

   1.   Decode the registration envelope.
   2.   Decode the registration payload.
   3.   Verify version == 1.
   4.   Verify type == 1.
   5.   Verify alg is allowed.
   6.   Verify rp_hash == SHA256(expected_rp_id).
   7.   Verify the registration nonce is valid and unused.
   8.   Verify the signature using pk.
   9.   If duress_pk is present, verify duress_signature using
        duress_pk.
   10.  Store the normal public key and initial key state.
   11.  If present, store the duress public key and initial duress key
        state.

11.3.  Initial Server Key State

   For each registered device, the verifier SHOULD store:

      kid
      current_pk
      key_seq = 0
      last_ctr = 0
      revoked = false

   If duress is registered:

      duress_pk
      duress_key_seq = 0
      duress_last_ctr = 0

12.  Verifiable Asymmetric Forward-Secure Ratchet

   PPS defines a practical asymmetric forward-secure ratchet using
   signed key rotation.

12.1.  Ratchet Principle

   The Authenticator maintains a current signing key pair:

      K_t = (sk_t, pk_t)

   When generating a Pulse, the Authenticator generates a next key
   pair:

      K_{t+1} = (sk_{t+1}, pk_{t+1})

   The Pulse payload includes:

      pk = pk_t
      next_pk = pk_{t+1}
      rot_seq = t + 1

   The Pulse is signed with sk_t.

   Because the signed payload includes next_pk, the signature
   authorizes rotation from pk_t to pk_{t+1}.

   After creating the Pulse, the Authenticator SHOULD delete sk_t and
   store sk_{t+1}.

   The verifier, after successful verification, updates:

      current_pk = next_pk
      key_seq = rot_seq

12.2.  Rotation Sequence

   The rotation sequence is a monotonic unsigned 64-bit integer.

   After registration:

      key_seq = 0

   Each rotation increments the sequence by one:

      new_key_seq = old_key_seq + 1

   A verifier MUST reject a Pulse whose rot_seq is not exactly one
   greater than the working key sequence after processing any rotation
   certificates.

12.3.  Rotation Certificates

   Rotation certificates allow an Authenticator to rotate locally
   before the verifier has observed the previous Pulse.

   A rotation certificate is signed by the old key and authorizes a
   new key.

      rotation-payload = {
        1: 1,
        2: 3,
        3: from_pk,
        4: to_pk,
        5: seq,
        6: epoch,
        7: exp,
        ? 8: rp_hash,
        ? 9: nonce
      }

   The certificate signature is made by from_sk.

   A verifier processing certificates MUST:

   1.  Set working_pk to the stored current_pk.
   2.  Set working_seq to the stored key_seq.
   3.  For each certificate:
       o  decode rotation-payload
       o  verify version == 1
       o  verify type == 3
       o  verify from_pk == working_pk
       o  verify seq == working_seq + 1
       o  verify expiration according to policy
       o  verify signature using working_pk
       o  set working_pk = to_pk
       o  set working_seq = seq
   4.  After all certificates, require pulse.pk == working_pk.
   5.  Verify the Pulse signature.
   6.  Verify pulse.rot_seq == working_seq + 1.
   7.  Update stored key state to pulse.next_pk and pulse.rot_seq.

12.4.  Secure Deletion

   An Authenticator claiming forward-secure operation MUST make a
   best-effort attempt to securely delete old private key material
   after rotation.

   Secure hardware-backed storage is RECOMMENDED.

   Verifiers cannot fully enforce secure deletion.

13.  Pulse Generation

13.1.  Inputs

   An Authenticator generating a Pulse requires:

      rp_id
      current private key
      current public key
      kid
      nonce, optional but RECOMMENDED
      context object, optional
      policy object, optional
      amount_minor, optional
      duress flag, optional

13.2.  Single-Device Pulse Generation

   The Authenticator MUST:

   1.   Compute the current epoch.
   2.   Increment the operation counter.
   3.   Generate a next Ed25519 key pair.
   4.   Construct the Pulse payload.
   5.   Encode the payload using deterministic CBOR.
   6.   Sign the encoded payload with the current private key.
   7.   Construct the envelope.
   8.   Encode the envelope using deterministic CBOR.
   9.   Encode the envelope as base64url if textual transport is
        required.
   10.  Delete the current private key after successful Pulse
        creation, subject to local recovery policy.

13.3.  Duress Pulse Generation

   If the user activates duress mode, the Authenticator MUST sign
   using the duress private key instead of the normal private key.

   The Pulse payload MUST NOT contain an explicit duress flag.

   Duress detection is performed by the verifier based on the public
   key used.

   The duress key MAY have its own forward-secure ratchet state.

13.4.  Threshold Pulse Generation

   In threshold mode:

   1.  The initiating device constructs the Pulse payload.
   2.  The payload includes gid and thr.
   3.  The payload omits single-device ratchet fields.
   4.  Each required signer signs the same message.
   5.  Signatures are collected into the signature map.
   6.  The final envelope is transmitted to the verifier.

   Threshold mode in version 1 uses static registered signer keys
   unless a future extension defines per-signer ratcheting.

14.  Pulse Verification

   A verifier MUST perform validation in a strict order.

14.1.  Token Parsing

   The verifier MUST:

   1.  Decode the Pulse Token from base64url if textual.
   2.  Decode the CBOR envelope.
   3.  Extract message, signatures, and optional certificates.
   4.  Decode message as a Pulse payload.

   If parsing fails, the verifier MUST reject the token.

14.2.  Common Payload Checks

   The verifier MUST check:

   1.   version == 1
   2.   type == 2
   3.   alg is allowed
   4.   rp_hash == SHA256(expected_rp_id)
   5.   epoch is within allowed skew
   6.   current epoch is not greater than exp
   7.   if a session nonce is required, nonce matches and is unused
   8.   if context is required, ctx matches expected context hash
   9.   if policy is required, pol matches expected policy hash
   10.  if AmountMark is required, amt matches expected amount modulo
        100

14.3.  Single-Device Verification

   For single-device ratchet mode:

   1.   Load device state by kid.
   2.   Reject if device is revoked.
   3.   Process optional rotation certificates.
   4.   Require pk to equal the working public key.
   5.   Verify the signature in the signature map using pk.
   6.   Require the signature map to contain exactly one signature for
        kid.
   7.   Reject if ctr <= last_ctr.
   8.   Reject if rot_seq != working_key_seq + 1.
   9.   If Trust Code was entered, verify it after signature
        verification.
   10.  Update state:
        o  current_pk = next_pk
        o  key_seq = rot_seq
        o  last_ctr = ctr

   State updates MUST be atomic.

14.4.  Duress Verification

   A verifier MAY maintain a separate duress key state for a device.

   If the working public key matches the stored duress public key, the
   verifier MUST classify the Pulse as duress.

   The verifier MUST NOT return an explicit duress indicator to the
   client.

   The verifier MAY:

   o  return an outwardly normal success response
   o  restrict sensitive operations
   o  limit transaction amounts
   o  trigger silent alerts
   o  require delayed settlement
   o  request step-up authentication later

   The verifier SHOULD apply the same response latency and response
   shape for duress and normal success.

14.5.  Threshold Verification

   For threshold mode:

   1.  Load threshold group by gid.
   2.  Reject if group is revoked.
   3.  Verify thr equals the stored group threshold.
   4.  Verify the signature map contains at least thr distinct
       signatures.
   5.  Verify each signature corresponds to a registered group signer.
   6.  Verify each signature over the same message.
   7.  Reject if ctr <= group_last_ctr.
   8.  Update group_last_ctr = ctr.

   Threshold mode does not require pk, next_pk, or rot_seq.

15.  Trust Code

   The Trust Code is a short numeric value derived from the
   cryptographic proof.

   It is intended for human comparison and optional typed fallback.

   The Trust Code is not a replacement for the full Pulse Token.

15.1.  Trust Input

   For single-device mode:

      trust_ikm = signature

   where signature is the single signature over the Pulse message.

   For threshold mode:

      trust_ikm = SHA256(cbor(signatures))

   where signatures is the complete signature map after collecting the
   required signatures.

15.2.  Trust Salt

      trust_salt = SHA256(
        utf8(rp_id)
        || 0x00
        || nonce_or_empty
        || 0x00
        || uint64_be(epoch)
      )

   If no nonce is present, nonce_or_empty is the empty byte string.

15.3.  Trust OKM

      trust_okm = HKDF-SHA256(
        ikm = trust_ikm,
        salt = trust_salt,
        info = utf8("PPS/trust/v1"),
        length = 32
      )

15.4.  Numeric Trust Code Length

   The default Trust Code length is 8 digits.

   If an AmountMark is present, the total length MUST be at least 8
   digits.

   The base signature-derived portion MUST be at least 6 digits.

      total_digits = base_digits + amount_digits
      amount_digits = 2 if AmountMark is present, otherwise 0
      base_digits >= 6

15.5.  Unbiased Decimal Derivation

   Implementations MUST avoid modulo bias when deriving decimal
   digits.

   The following algorithm is normative for the mandatory suite:

      DecimalFromOKM(okm, digits):

        modulus = 10 ^ digits
        limit = 2^64 - (2^64 mod modulus)

        for i from 0 to 255:
          block = SHA256(
            okm
            || 0x00
            || uint8(i)
            || utf8("PPS/decimal")
          )

          x = uint64_be(block[0:8])

          if x < limit:
            value = x mod modulus
            return zero_padded_decimal(value, digits)

        fail

   Failure is cryptographically negligible.

15.6.  AmountMark Embedding

   If an amount is known, the AmountMark is:

      amount_mark = amount_minor mod 100

   represented as two decimal digits.

   The final Trust Code is:

      trust_code = base_code || amount_mark

   Example:

      base_code = 493718
      amount_minor = 2500067
      amount_mark = 67
      trust_code = 49371867

15.7.  Trust Code Verification

   A verifier MUST derive the expected Trust Code only after
   successful signature verification.

   Trust Code comparison MUST be constant-time.

16.  Trust Phrase

   Trust Phrase support is OPTIONAL.

   If implemented, the RECOMMENDED construction is:

      phrase_index_0 = uint16_be(trust_okm[8:10])  mod 2048
      phrase_index_1 = uint16_be(trust_okm[10:12]) mod 2048
      phrase_index_2 = uint16_be(trust_okm[12:14]) mod 2048

   Each index selects a word from the BIP39 English wordlist.

   The Trust Phrase is:

      word_0 + " " + word_1 + " " + word_2

   Example:

      river bronze lantern

   The Trust Phrase is intended for visual comparison and SHOULD NOT
   be used as the sole authentication factor.

17.  Context Binding

   Context binding allows a Pulse to be bound to environmental or
   session data.

17.1.  Context Hash

   The context object is encoded using deterministic CBOR and hashed:

      ctx = SHA256(cbor(context-object))

17.2.  Recommended Context Fields

   +--------------------------+----------------------------------+
   | Field                    | Meaning                          |
   +--------------------------+----------------------------------+
   | session_id               | server session identifier        |
   | tx_hash                  | transaction hash                 |
   | ble_hash                 | BLE proximity beacon hash        |
   | acoustic_hash            | acoustic nonce hash              |
   | ip_hash                  | privacy-preserving IP hash       |
   | ua_hash                  | user agent hash                  |
   | device_integrity_hash    | device integrity measurement     |
   | custom_hash              | application-specific hash        |
   +--------------------------+----------------------------------+

17.3.  Proximity Example

   An RP web client may broadcast a temporary BLE beacon:

      ble_beacon = random_nonce || timestamp

   The Authenticator receives the beacon and includes:

      ble_hash = SHA256(ble_beacon)

   The verifier compares the expected hash.

   This does not prove physical presence with mathematical certainty,
   but increases phishing and relay cost.

18.  Policy Binding

   Policy binding ensures that the Pulse was generated under the
   security policy expected by the RP.

18.1.  Policy Hash

      pol = SHA256(cbor(policy-object))

18.2.  Policy Verification

   If the RP issued a policy, the verifier MUST compare the Pulse pol
   field with the expected policy hash.

   If the values differ, the Pulse MUST be rejected.

18.3.  Downgrade Protection

   Verifiers SHOULD NOT accept Pulses with weaker policy hashes than
   required for the requested operation.

   For high-value transactions, the policy SHOULD include:

      require_amount_mark = true
      require_biometric = true
      require_secure_enclave = true
      threshold >= 2, if applicable
      max_amount_minor = expected maximum

19.  Transaction Binding

   PPS does not mandate a transaction format.

   However, when transaction binding is used, the RP SHOULD define a
   canonical transaction object and compute:

      tx_hash = SHA256(cbor(transaction-object))

   The tx_hash is included in the context object.

   If AmountMark is used, the transaction amount modulo 100 is
   embedded in the Trust Code.

   The verifier MUST verify both:

   1.  ctx matches expected context including tx_hash
   2.  amt matches expected amount modulo 100, if present

20.  Nonce and Replay Protection

20.1.  Online Mode

   In online mode, the verifier SHOULD issue a fresh nonce for each
   authentication attempt.

   The nonce MUST be:

   o  at least 128 bits
   o  generated by a CSPRNG
   o  single-use
   o  time-limited

   The RECOMMENDED nonce lifetime is 60 seconds.

20.2.  Offline Mode

   In offline mode, the nonce MAY be omitted.

   Offline Pulses MUST be protected by:

   o  monotonic counter
   o  expiration
   o  strict rate limiting
   o  risk-based policy

   Verifiers SHOULD treat offline Pulses as lower assurance unless
   additional context is present.

20.3.  Counter

   The operation counter ctr is a monotonic unsigned 64-bit integer.

   A verifier MUST reject any Pulse where:

      ctr <= stored_last_ctr

   The counter MUST be updated only after successful cryptographic
   verification.

21.  Error Handling

   Implementations SHOULD use stable internal error codes.

   +--------------------------+----------------------------------+
   | Error                    | Meaning                          |
   +--------------------------+----------------------------------+
   | invalid_token            | parsing failure                  |
   | bad_version              | unsupported version              |
   | bad_type                 | incorrect message type           |
   | bad_alg                  | algorithm not allowed            |
   | bad_rp                   | RP hash mismatch                 |
   | bad_epoch                | epoch outside allowed window     |
   | expired                  | expiration passed                |
   | bad_nonce                | nonce missing/invalid/reused     |
   | bad_context              | context hash mismatch            |
   | bad_policy               | policy hash mismatch             |
   | bad_amount               | AmountMark mismatch              |
   | unknown_key              | public key not recognized        |
   | bad_certificate          | rotation certificate invalid     |
   | bad_rotation_sequence    | rotation sequence mismatch       |
   | counter_replay           | counter not increasing           |
   | bad_signature            | signature verification failed    |
   | bad_trust_code           | Trust Code mismatch              |
   | revoked_device           | device key revoked               |
   | revoked_group            | threshold group revoked          |
   | threshold_not_met        | insufficient valid signatures    |
   | rate_limited             | too many attempts                |
   | internal_error           | server error                     |
   +--------------------------+----------------------------------+

   Verifiers SHOULD return a generic error to unauthenticated clients:

      authentication_failed

   Detailed errors MAY be logged server-side.

22.  Optional URI Scheme

   PPS defines the following URI scheme for challenge delivery:

      pulseproof://

22.1.  Challenge URI

      pulseproof://challenge?
        v=1
        &rp=example.com
        &nonce=base64url
        &epoch=uint
        &session=base64url
        &tx=base64url
        &policy=base64url

   Parameters:

   +-----------+-------------+----------------------------------+
   | Parameter | Required    | Description                      |
   +-----------+-------------+----------------------------------+
   | v         | YES         | protocol version, MUST be 1      |
   | rp        | YES         | RP identifier                    |
   | nonce     | RECOMMENDED | base64url nonce                  |
   | epoch     | OPTIONAL    | server time hint                 |
   | session   | OPTIONAL    | base64url session identifier     |
   | tx        | OPTIONAL    | base64url transaction hash       |
   | policy    | OPTIONAL    | base64url CBOR policy object     |
   +-----------+-------------+----------------------------------+

   All base64url values MUST omit padding.

23.  HTTP Binding

   HTTP binding is OPTIONAL.

   When used, the following JSON structure is RECOMMENDED.

23.1.  Challenge Response

      {
        "v": 1,
        "rp": "example.com",
        "nonce": "base64url",
        "epoch": 58972480,
        "session_id": "base64url",
        "tx_hash": "base64url",
        "policy": "base64url"
      }

23.2.  Verification Request

      {
        "session_id": "base64url",
        "pulse_token": "base64url",
        "trust_code": "49371867"
      }

23.3.  Verification Response

   For normal success:

      {
        "status": "ok"
      }

   For duress success, the outward response SHOULD be
   indistinguishable:

      {
        "status": "ok"
      }

   The verifier MAY include a Trust Phrase after successful
   verification if the UI requires mutual visual confirmation.

24.  Conformance

24.1.  Core Authenticator

   A conformant PPS Core Authenticator MUST:

   1.  Support PPS-ED25519-CBOR30.
   2.  Generate Ed25519 keys.
   3.  Generate deterministic CBOR structures.
   4.  Sign Pulse messages.
   5.  Derive Trust Codes.
   6.  Increment counters monotonically.
   7.  Support nonce-bound online Pulses.

   A conformant PPS Core Authenticator SHOULD:

   1.  Support secure hardware key storage.
   2.  Support forward-secure key rotation.
   3.  Support duress keys.
   4.  Support context and policy binding.
   5.  Support AmountMark.

24.2.  Core Verifier

   A conformant PPS Core Verifier MUST:

   1.  Support PPS-ED25519-CBOR30.
   2.  Verify Ed25519 signatures.
   3.  Reject non-canonical CBOR.
   4.  Verify RP hash.
   5.  Verify epoch and expiration.
   6.  Verify counters.
   7.  Verify nonces when used.
   8.  Verify Trust Codes using constant-time comparison when Trust
       Codes are used.
   9.  Implement rate limiting.

   A conformant PPS Core Verifier SHOULD:

   1.  Support rotation certificates.
   2.  Support duress key states.
   3.  Support policy and context verification.
   4.  Support threshold groups.
   5.  Support transaction binding.

24.3.  Forward-Secure Profile

   An implementation claiming PPS Forward-Secure support MUST:

   1.  Rotate signing keys as specified in Section 12.
   2.  Include next_pk and rot_seq in single-device Pulses.
   3.  Make a best-effort secure deletion of previous private keys.
   4.  Support rotation certificates for offline catch-up.

24.4.  Duress Profile

   An implementation claiming PPS Duress support MUST:

   1.  Register a separate duress public key.
   2.  Sign Honey-Pulses with the duress private key.
   3.  Avoid including explicit duress indicators in Pulse payloads.
   4.  Return outwardly indistinguishable success responses when
       duress is detected.

24.5.  Threshold Profile

   An implementation claiming PPS Threshold support MUST:

   1.  Register threshold groups with signer keys and threshold
       values.
   2.  Require the stored threshold value, not the client-provided
       value alone.
   3.  Verify at least the required number of valid signatures.
   4.  Reject duplicate signer signatures.
   5.  Maintain a monotonic group counter.

25.  Security Considerations

25.1.  Shared Secret Removal

   PPS removes shared symmetric secrets from the verifier.

   A verifier database breach exposes public keys and metadata, not
   private keys.

   This is a significant improvement over TOTP shared secrets.

25.2.  Phishing Resistance

   PPS improves phishing resistance when used with nonce-bound
   challenges and full Pulse Token transmission.

   A Pulse is bound to:

   o  RP identifier
   o  nonce
   o  epoch
   o  counter
   o  context
   o  policy
   o  transaction hash, if used

   Manual Trust Code entry alone provides weaker protection.

25.3.  Trust Code Entropy

   A numeric Trust Code has limited entropy.

   Approximate entropy:

      6 digits  = 19.9 bits
      8 digits  = 26.6 bits
      10 digits = 33.2 bits
      12 digits = 39.9 bits

   Therefore, Trust Code-only fallback MUST be protected by strict
   rate limiting.

   For high-security operations, the full Pulse Token SHOULD be used.

25.4.  Replay Protection

   Replay protection is provided by:

   o  nonce
   o  epoch
   o  expiration
   o  monotonic counter
   o  rotation sequence

   Verifiers MUST mark nonces as used.

   Verifiers MUST update counters and key states atomically.

25.5.  Forward Secrecy Limitations

   The PPS forward-secure ratchet depends on secure deletion of old
   private keys.

   If an attacker continuously controls the Authenticator, forward
   secrecy may not protect future operations.

   Secure hardware and user-presence checks are strongly recommended.

25.6.  Rotation Certificate Abuse

   Rotation certificates are signed authorizations to advance a key
   state.

   If an attacker obtains a valid rotation certificate before the
   verifier does, they may cause the verifier to advance key state.

   This can cause denial-of-service or state desynchronization but
   does not by itself reveal private keys.

   Mitigations:

   o  bind certificates to RP hash
   o  bind certificates to nonce when online
   o  use short expiration windows
   o  rate-limit state changes
   o  require user notification for key rotation anomalies

25.7.  Duress Safety

   Duress detection MUST remain hidden from the client response.

   Verifiers SHOULD equalize:

   o  HTTP status
   o  response body shape
   o  response latency
   o  cookie behavior
   o  UI messages

   Silent alerts SHOULD respect user safety and legal requirements.

25.8.  Threshold Mode

   Threshold mode increases resilience against single-device
   compromise.

   However, if all threshold devices are compromised or coerced,
   threshold protection fails.

   Offline threshold communication channels such as BLE, NFC, or QR
   should be protected against relay when possible.

25.9.  Malware and User Approval

   If malware can trick the user into approving a malicious
   transaction, PPS cannot fully prevent fraud.

   Mitigations:

   o  display transaction details
   o  use AmountMark
   o  use Trust Phrase
   o  enforce transaction limits
   o  use risk-based step-up
   o  use device integrity signals

25.10.  Clock Skew

   Large clock-skew windows increase replay risk.

   Verifiers SHOULD use NTP or another trusted time source.

   Authenticators SHOULD synchronize time when possible.

25.11.  CBOR Parsing

   Implementations MUST reject non-canonical CBOR.

   CBOR parsers should be tested against malformed input, duplicate
   keys, indefinite lengths, and oversized structures.

25.12.  Post-Quantum Considerations

   Ed25519 is not believed to be secure against large-scale quantum
   computers.

   Deployments requiring long-term quantum resistance SHOULD plan
   migration to ML-DSA or hybrid suites.

26.  Privacy Considerations

26.1.  Minimal Personal Data

   PPS fields SHOULD avoid personally identifiable information.

   Recommended:

      user_handle = opaque random identifier
      kid = opaque random identifier
      gid = opaque random identifier

26.2.  Pairwise Identifiers

   Authenticators SHOULD use pairwise kid values per RP to reduce
   cross-service correlation.

26.3.  Context Privacy

   Context fields such as IP address or user agent SHOULD be hashed or
   salted if stored.

   Example:

      ip_hash = SHA256(ip || daily_salt)

   Daily salts reduce long-term correlation.

26.4.  Duress Privacy

   Duress events SHOULD be accessible only to authorized security
   personnel.

   Audit logs SHOULD be protected and access-controlled.

27.  IANA Considerations

   This document makes the following requests.

27.1.  Media Type Registration

   This document requests registration of the following media type:

      application/pulseproof+cbor

   Purpose:

      Binary encoding of PulseProof Sentinel envelopes.

27.2.  URI Scheme Registration

   This document requests registration of the following URI scheme:

      pulseproof

   Purpose:

      PulseProof Sentinel challenge and authentication handoff.

27.3.  Algorithm Registry

   This document defines a conceptual PulseProof Sentinel Algorithm
   Registry.

   Initial values:

      PPS-ED25519-CBOR30
      PPS-MLDSA44-CBOR30
      PPS-MLDSA65-CBOR30
      PPS-HYB-ED25519-MLDSA44-CBOR30

   Future updates SHOULD specify:

   o  signature algorithm
   o  public key format
   o  signature format
   o  hash algorithm
   o  KDF
   o  epoch length
   o  security considerations

28.  Normative References

   [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>.

   [RFC4086]  Eastlake 3rd, D., Schiller, J., and S. Crocker,
              "Randomness Requirements for Security", BCP 106,
              RFC 4086, DOI 10.17487/RFC4086, June 2005,
              <https://www.rfc-editor.org/info/rfc4086>.

   [RFC4648]  Josefsson, S., "The Base16, Base32, and Base64 Data
              Encodings", RFC 4648, DOI 10.17487/RFC4648,
              October 2006,
              <https://www.rfc-editor.org/info/rfc4648>.

   [RFC5869]  Krawczyk, H. and P. Eronen, "HMAC-based Extract-and-
              Expand Key Derivation Function (HKDF)", RFC 5869,
              DOI 10.17487/RFC5869, May 2010,
              <https://www.rfc-editor.org/info/rfc5869>.

   [RFC8032]  Josefsson, S. and I. Liusvaara, "Edwards-Curve Digital
              Signature Algorithm (EdDSA)", RFC 8032,
              DOI 10.17487/RFC8032, January 2017,
              <https://www.rfc-editor.org/info/rfc8032>.

   [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>.

   [RFC8949]  Bormann, C. and P. Hoffman, "Concise Binary Object
              Representation (CBOR)", STD 94, RFC 8949,
              DOI 10.17487/RFC8949, December 2020,
              <https://www.rfc-editor.org/info/rfc8949>.

   [FIPS180-4]
              National Institute of Standards and Technology,
              "Secure Hash Standard (SHS)", FIPS PUB 180-4,
              August 2015.

   [FIPS204]  National Institute of Standards and Technology,
              "Module-Lattice-Based Digital Signature Standard",
              FIPS PUB 204, August 2024.

29.  Informative References

   [RFC8610]  Birkholz, H., Vigano, C., and C. Bormann, "Concise Data
              Definition Language (CDDL): A Notational Convention to
              Express Concise Binary Object Representation (CBOR) and
              JSON Data Structures", RFC 8610, DOI 10.17487/RFC8610,
              June 2019,
              <https://www.rfc-editor.org/info/rfc8610>.

   [WebAuthn] W3C, "Web Authentication: An API for accessing Public
              Key Credentials",
              <https://www.w3.org/TR/webauthn/>.

   [FIDO2]    FIDO Alliance, "FIDO2: WebAuthn and CTAP",
              <https://fidoalliance.org/fido2/>.

   [RFC6238]  M'Raihi, D., Machani, S., Pei, M., and J. Rydell,
              "TOTP: Time-Based One-Time Password Algorithm",
              RFC 6238, DOI 10.17487/RFC6238, May 2011,
              <https://www.rfc-editor.org/info/rfc6238>.

   [BIP39]    Bitcoin Improvement Proposal 39, "Mnemonic code for
              generating deterministic keys",
              <https://github.com/bitcoin/bips/blob/master/
              bip-0039.mediawiki>.

Appendix A.  CDDL Summary

   This appendix is informative.

      uint64 = uint .size 8

      alg = tstr
      bstr-id = bstr .size (16..64)
      rp-hash = bstr .size 32
      ctx-hash = bstr .size 32
      pol-hash = bstr .size 32
      public-key = bstr .size 32
      signature = bstr .size 64
      nonce = bstr .size (16..64)
      amount-mark = uint .le 99

      registration-payload = {
        1: 1,
        2: 1,
        3: alg,
        4: bstr-id,
        5: rp-hash,
        6: public-key,
        7: bstr-id,
        8: nonce,
        9: uint64,
        ? 10: tstr,
        ? 11: bstr,
        ? 12: public-key
      }

      registration-envelope = {
        1: bstr .cbor registration-payload,
        2: signature,
        ? 3: signature
      }

      rotation-payload = {
        1: 1,
        2: 3,
        3: public-key,
        4: public-key,
        5: uint64,
        6: uint64,
        7: uint64,
        ? 8: rp-hash,
        ? 9: nonce
      }

      rotation-certificate = {
        1: bstr .cbor rotation-payload,
        2: signature
      }

      single-mode-fields = (
        4: bstr-id,
        9: public-key,
        10: public-key,
        15: uint64
      )

      threshold-mode-fields = (
        20: bstr-id,
        21: uint
      )

      pulse-payload = {
        1: 1,
        2: 2,
        3: alg,
        5: rp-hash,
        6: uint64,
        7: uint64,
        8: uint64,
        ? 11: nonce,
        ? 12: ctx-hash,
        ? 13: pol-hash,
        ? 14: amount-mark,
        (single-mode-fields // threshold-mode-fields)
      }

      signature-map = {
        * bstr-id => signature
      }

      pulse-envelope = {
        1: bstr .cbor pulse-payload,
        2: signature-map,
        ? 3: [ + rotation-certificate ]
      }

      context-object = {
        ? 1: bstr,
        ? 2: bstr,
        ? 3: bstr,
        ? 4: bstr,
        ? 5: bstr,
        ? 6: bstr,
        ? 7: bstr,
        ? 8: bstr
      }

      policy-object = {
        ? 1: uint,
        ? 2: bool,
        ? 3: bool,
        ? 4: uint,
        ? 5: uint64,
        ? 6: bool,
        ? 7: uint,
        ? 8: bool,
        ? 9: bool,
        ? 10: bool
      }

Appendix B.  Recommended Server State

   For single-device ratchet mode:

      DeviceState {
        kid
        user_handle
        current_pk
        key_seq
        last_ctr
        revoked
        created_at
        updated_at
      }

   For duress:

      DuressState {
        kid
        duress_pk
        duress_key_seq
        duress_last_ctr
      }

   For threshold groups:

      ThresholdGroupState {
        gid
        rp_hash
        signers: map[kid => public_key]
        threshold
        last_ctr
        revoked
        created_at
        updated_at
      }

   For nonces:

      NonceState {
        nonce
        session_id
        created_at
        expires_at
        used
      }

Appendix C.  Recommended Verification Order

   The following order is RECOMMENDED:

      1.  Parse token.
      2.  Check version and type.
      3.  Check algorithm.
      4.  Check RP hash.
      5.  Check epoch and expiration.
      6.  Check nonce.
      7.  Check context hash.
      8.  Check policy hash.
      9.  Check AmountMark.
      10. Process rotation certificates, if present.
      11. Verify signatures.
      12. Verify counters.
      13. Verify rotation sequence.
      14. Verify Trust Code, if entered.
      15. Update state atomically.
      16. Return outward response.

Appendix D.  Patent and Implementation Notice

   This specification describes novel combinations of asymmetric
   time-bound proofs, forward-secure key ratcheting, silent duress
   signaling, threshold offline co-signing, policy binding, context
   binding, and transaction-visible trust codes.

   No representation is made regarding the patent status of these
   techniques in any jurisdiction.

   Implementers intending to patent, commercialize, or deploy this
   protocol in regulated environments SHOULD perform prior-art
   searches and obtain appropriate legal review.

   This specification is experimental and has not been independently
   audited.

Author's Address

   H. Hezami
   Email: hossein.hezami@gmail.com