Tags: record, crypto
Seal records need portable authorship proof that survives transport
boundaries: a verifier signs a specific Plex-record digest, and any
implementation can check the signature from record bytes alone. This
spec defines verifier, signing-secret, and signature rules used by Seal
records in the current .H3 suite. It signs a 32-byte BLAKE3
digest with secp256k1 Schnorr arithmetic and encodes public protocol
values with B64A.
This document is for record and crypto implementors. After reading it, an implementor should be able to generate verifier text, derive signing material, produce signatures, and verify Seal-record signatures consistently across languages.
Key generation, signing-secret derivation, signing, and verification.
V.<b64a>.H3, where payload is a 32-byte
public x-coordinate.&.<b64a>.H3, where payload is
32 bytes of secret derivation material.A verifier is public. A signing secret is private local material used to derive a signing scalar.
G.n.p.msg32, a 32-byte BLAKE3 digest of signed
bytes.bytes(x), the 32-byte big-endian
encoding of a scalar or field element.BLAKE3 derive-key context strings:
| Context | Use |
|---|---|
lace-๐ง/adhoc-key |
signing-secret derivation |
lace-๐ง/aux |
signing auxiliary randomness tag |
lace-๐ง/nonce |
nonce tag |
lace-๐ง/challenge |
challenge tag |
derive_key(context, input) means the first 32 bytes of
BLAKE3 derive-key output for that context and input. Context strings are
encoded as the exact UTF-8 bytes of the displayed text.
Implementations MUST generate random signing scalars as follows:
d0 with
0 < d0 < n.P = d0*G.P.y is odd, set d = n - d0; otherwise
set d = d0.d and verifier bytes are
bytes((d*G).x).The even-y convention makes one verifier text correspond to one public point.
To derive signing material from a signing secret payload, implementations MUST:
lace-๐ง/adhoc-key.0 < d0 < n.Inputs are signing scalar d, verifier x-coordinate
Px, message digest msg32, and fresh nonzero
32-byte auxRand32.
Implementations MUST sign as follows:
t = derive_key("lace-๐ง/aux", auxRand32).mask = t xor bytes(d).k0 = derive_key("lace-๐ง/nonce", mask || Px || msg32) mod n;
reject zero.R = k0*G; use k = n-k0 when
R.y is odd, else k = k0.e = derive_key("lace-๐ง/challenge", bytes(R.x) || Px || msg32) mod n.s = (k + e*d) mod n.bytes(R.x) || bytes(s).auxRand32 MUST be unique per signature and MUST NOT be
all zero.
Inputs are verifier x-coordinate Px, signature
r||s, and message digest msg32.
Implementations MUST verify as follows:
Px >= p, r >= p, or
s >= n.P from Px using the even-y
root; reject Px values that do not identify a secp256k1
point.e = derive_key("lace-๐ง/challenge", bytes(r) || Px || msg32) mod n.R' = s*G - e*P.R'.y.R'.x == r.Implementations MUST use constant-time scalar and point operations.
Reject examples include an empty signing secret, all-zero auxiliary
value, Px >= p, Px with no curve point,
r >= p, s >= n, odd recovered
R', and any signature whose recomputed R'.x
differs from r.