HPPR-009 ยท HSB3 Signatures
HSB3 is a BIP-340 style Schnorr signature over secp256k1.
HSB3 uses BLAKE3-256 for all hashing and key derivation tags.
Reference code lives in rust/libs/hsb3.
Parameters
- Curve: secp256k1 (
y^2 = x^3 + 7 mod p) - Order:
n - Generator:
G - Hash primitive:
BLAKE3.derive_key - Tags:
hppr-๐ง/auxhppr-๐ง/noncehppr-๐ง/challenge
Helper notation:
bytes(x): 32-byte big-endian encoding of scalar or field elementbeInt(b): integer from 32-byte big-endian bytestagged(tag, msg): first 32 bytes ofderive_key(tag, msg)
Key Generation
- Draw random 32-byte
d0; reject0andd0 >= n. - Set
d = d0, computeP = d*G. - If
P.yis odd, setd = n - dand recomputeP. - Private key is
d; public key isbytes(P.x).
The even-y convention is mandatory.
Deterministic Key Derivation
Derive a keypair from arbitrary secret bytes.
- Initialize BLAKE3 derive mode with tag
hppr-๐ง/adhoc-key. - Feed secret bytes.
- Enter XOF mode.
- Rejection sample 32-byte candidates until
0 < d0 < n. - Apply the same even-
ynormalization as key generation.
Conformance:
- MUST use tag
hppr-๐ง/adhoc-keyexactly - MUST use XOF rejection sampling
- MUST apply even-
yconvention - MUST reject empty secret
Ring1 Adhoc Derivation (Argon2id)
Ring1 secret tokens derive through Argon2id first.
Token format:
V.<b64a>.H3
Argon2id inputs:
- password: UTF-8 original secret
- salt: first 16 bytes of
BLAKE3.derive_key("hppr-๐ง/phc-salt", "<ring1>/<repo-vkey>") - params:
$argon2id$v=19$m=<m>,t=<t>,p=<p>$ - output length: 32
If HELLO omits PHC, defaults are
m=12288, t=3, p=1.
Then derive HSB3 key from:
<derived-token>/<ring1>/<repo-vkey>
Signing
Inputs:
msg32: BLAKE3-256 digest of message bytes- private key
d auxRand32: fresh random 32 bytes
Rules:
auxRand32MUST be unique per signature- explicit all-zero aux input is invalid
Algorithm:
t = tagged(aux, auxRand32)mask = t xor bytes(d)k0 = tagged(nonce, mask || Px || msg32) mod n; rejectk0 = 0R = k0*G; ifR.yodd thenk = n-k0, elsek = k0e = tagged(challenge, bytes(R.x) || Px || msg32) mod ns = (k + e*d) mod n- signature bytes are
bytes(R.x) || bytes(s)
Text form uses B64A.
Verification
Inputs: signature r||s, public key Px,
and msg32.
- Parse
r,s; rejectr >= pors >= n. - Recover
PfromPxand take even-yroot. e = tagged(challenge, bytes(r) || Px || msg32) mod nR' = s*G - e*P; reject infinity or oddy.- Accept iff
R'.x == r.
Conformance
Implementations:
- MUST use the three UTF-8 tags exactly
- MUST use BLAKE3-256 message digests
- MUST preserve even-
yconvention in keygen/sign/verify - MUST emit 64-byte signatures and 32-byte public keys
- MUST reject invalid scalars and coordinates
- MUST use constant-time scalar and point operations
- MUST NOT reuse
auxRand32