Tags: record, crypto
Seal records need portable byte-control evidence that survives
transport boundaries: a secret key makes a Mark over one field-bearing
Plex-record digest, and any implementation can check that Mark with the
public verification key from record bytes alone. This spec defines
secret-key, verification-key, and Mark rules used by Seal records in the
current .H3 suite. It marks a 32-byte BLAKE3 digest with
secp256k1 Schnorr arithmetic and encodes protocol values with B64A.
This document is for record and crypto implementors. After reading it, an implementor should be able to generate canonical secret-key text, derive its verification key, make Marks, and check Marks consistently across languages.
Secret-key generation and scalar derivation, verification-key derivation, Mark construction, and Mark checking.
&.<b64a>.H3, where the payload is
32 secret bytes from which the internal scalar is derived.V.<b64a>.H3, where the payload
is a 32-byte public x-coordinate.A secret key is private local material. A verification key and Mark are public. Implementations MUST require the exact key prefixes and suffixes, decoded lengths, and canonical B64A encoding shown above.
G.n.p.msg32, a selected 32-byte digest. For a
Seal, it is the 32 decoded digest bytes carried by the immediately
embedded Plex record’s P.<b64a>.H3 glyph. Under 010,
those bytes are the BLAKE3-256 digest of the Plex record’s canonical
payload after its record glyph line, including every application field
occurrence and the complete embedded Blob.bytes(x), the 32-byte big-endian
encoding of a scalar or field element.All 32-byte scalar and field-element inputs are interpreted as unsigned big-endian integers. All scalar and field-element outputs are fixed-width 32-byte big-endian bytes.
BLAKE3 derive-key context strings:
| Context | Use |
|---|---|
lace-🖧/adhoc-key |
secret-key scalar derivation |
lace-🖧/aux |
Mark 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.
Implementation-provided random secret-key generators MUST choose 32
bytes with a CSPRNG and encode those exact bytes as canonical
&.<b64a>.H3 text. Applications may instead supply
a 32-byte secret-key payload, including output from an
application-chosen KDF. Its secrecy and resistance to guessing are the
application’s responsibility: a public verification key permits offline
testing of candidate payloads. The payload, not a directly generated
secp256k1 scalar, is the supported serialized private-key form.
To derive the normalized scalar d from a secret-key
payload, implementations MUST:
lace-🖧/adhoc-key.d0 satisfies
0 < d0 < n.P = d0*G.P.y is odd, set d = n - d0; otherwise
set d = d0.The scalar is an implementation detail and has no separate public text form.
Given the normalized scalar d, the verification-key
bytes are bytes((d*G).x). The even-y normalization makes
one verification-key text correspond to one public point.
The complete derivation is:
&.<b64a>.H3 secret-key text
-> 32 secret bytes
-> normalized scalar
-> 32-byte verification key
-> V.<b64a>.H3 verification-key text
Inputs are normalized scalar d, its verification-key
x-coordinate Px, message digest msg32, and a
32-byte auxRand32 value. Implementations SHOULD use fresh
CSPRNG-generated auxiliary bytes for ordinary Mark construction. Fixed
values, including all zero, MAY be used for reproducible Marks and test
vectors; auxiliary randomness is supplemental to the nonce derivation’s
secret and message inputs.
Implementations MUST make a Mark as follows:
t = derive_key("lace-🖧/aux", auxRand32).mask = t xor bytes(d).k0 = derive_key("lace-🖧/nonce", mask || Px || msg32) mod n;
if it is zero, construction fails and the caller may retry with a
different auxRand32.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).Inputs are verification-key x-coordinate Px, Mark
r||s, and message digest msg32.
Implementations MUST check 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.
This algorithm-level vector covers secret-key payload derivation,
even-y verification-key derivation, BLAKE3 message hashing, fixed
auxiliary input, Mark construction, and canonical text encoding. The
UTF-8 source text is included only to make the msg32 digest
reproducible; Mark construction receives the digest, not the source
text.
| Value | Canonical text or hexadecimal bytes |
|---|---|
| secret payload bytes | 0707070707070707070707070707070707070707070707070707070707070707 |
| secret key | &.1lS71lS71lS71lS71lS71lS71lS71lS71lS71lS71lS.H3 |
| message source bytes | UTF-8 hello world |
msg32 bytes |
d74981efa70a0c880b8d8c1985d075dbcbf679b99a5f9914e5aaf96b831a9e24 |
msg32 B64A |
qpb1wvSA38WBZOlPXT1qrxkrURbQNu_KuQguQtCQcYG |
auxRand32 bytes |
0101010101010101010101010101010101010101010101010101010101010101 |
auxRand32 B64A |
0G410G410G410G410G410G410G410G410G410G410G4 |
| verification-key bytes | 235ad8ab794e4ef501e3e84507775d2575285884c2736f9ad46bc89db67ee990 |
| verification key | V.8qgOfsaEJkK1tzX51sTT9NKdM8J2SrzQq6k8cRPzvP0.H3 |
| Mark bytes | ae3c9a3a46d745dd20e485ff6e259059db90cff31db06d79e0d1e3ab1df7ad841120e93b7190e73aa91a19a41843430266fc24fc4c1f6495f8345af75fcdbcb1 |
| Mark | gZnQE_RNHTpWu8N~RYMGMTjGo~CTh6qutD7ZfmssgOGH8E_wSP3cEf_Q6QGOGpC2Pkl_~4lVP9NtD5gsNxrxhG |
Lace-010 defines a Seal as a By structural field, a
Mark structural field, and one immediately embedded Plex.
By contains canonical verification-key text,
Mark contains the Mark text, and the Mark subject is the
digest from that Plex glyph. Application fields affect the subject
through their canonical Plex bytes and resulting digest.
In record and API terminology, By is the canonical
V.<b64a>.H3 verification key. The corresponding
private &.<b64a>.H3 value is the By-secret
value.
The byte-exact 010 Seal vector uses the same 32-byte 07
secret payload and 32-byte 01 auxiliary input as the
construction vector above. Its embedded Plex digest differs, so its
canonical Mark is:
t9QAUgyE1c391D6RsPH_gok2OXEPTRfcp4HOa_Wcyx8143adV34c1ReKeGmWXXn6ictYPxDz5ULgqz0RK4sbPl
This couples the algorithm vector to the record rule without assigning any application field a special role.
Reject examples include malformed or noncanonical secret-key or
verification-key text, a secret-key payload of the wrong length,
Px >= p, Px with no curve point,
r >= p, s >= n, odd recovered
R', and any Mark whose recomputed R'.x differs
from r. In particular, substituting the first known-answer
Mark above into the 010 Seal vector and recomputing only the outer Seal
hash MUST fail: that Mark is canonical and uses the same verification
key, but its subject is the hello world digest rather than
the embedded Plex digest.