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, . [RFC4086] Eastlake 3rd, D., Schiller, J., and S. Crocker, "Randomness Requirements for Security", BCP 106, RFC 4086, DOI 10.17487/RFC4086, June 2005, . [RFC4648] Josefsson, S., "The Base16, Base32, and Base64 Data Encodings", RFC 4648, DOI 10.17487/RFC4648, October 2006, . [RFC5869] Krawczyk, H. and P. Eronen, "HMAC-based Extract-and- Expand Key Derivation Function (HKDF)", RFC 5869, DOI 10.17487/RFC5869, May 2010, . [RFC8032] Josefsson, S. and I. Liusvaara, "Edwards-Curve Digital Signature Algorithm (EdDSA)", RFC 8032, DOI 10.17487/RFC8032, January 2017, . [RFC8174] Leiba, B., "Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words", BCP 14, RFC 8174, DOI 10.17487/RFC8174, May 2017, . [RFC8949] Bormann, C. and P. Hoffman, "Concise Binary Object Representation (CBOR)", STD 94, RFC 8949, DOI 10.17487/RFC8949, December 2020, . [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, . [WebAuthn] W3C, "Web Authentication: An API for accessing Public Key Credentials", . [FIDO2] FIDO Alliance, "FIDO2: WebAuthn and CTAP", . [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, . [BIP39] Bitcoin Improvement Proposal 39, "Mnemonic code for generating deterministic keys", . 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