Skip to main content

Layer 1 · BLS signatures & domain separation

Canonical reference: digstore-crypto::bls (host/producer, Chia AugScheme via chia-blsblst) and digstore-guest::attestation (verifier, pure-Rust bls12_381, wasm/zk-clean). The two are parity-locked by host-signed fixtures the guest must accept (tests/bls_fixtures.rs).

Scheme

Chia AugScheme over BLS12-381: G1 public key = 48 bytes, G2 signature = 96 bytes (bls.rs:14-29). aug_sign prepends the signer's public key + the Chia DST and hashes to G2. AugScheme's pubkey-binding is the base defense; the role DSTs below are defense-in-depth on top of it.

validate_public_key rejects the canonical G1 identity / point-at-infinity (0xc0 || 0…, 48 bytes) and non-canonical bytes — the rogue-key defense (bls.rs:117-138).

The five role DSTs

Every signing message prepends a distinct role tag so a signature for one role can never replay as another (bls.rs:152-185).

RoleDSTSigning messageReference
pushb"digstore:push:v1"SHA-256(PUSH_DST || root(32) || store_id(32)) → 32Bbls.rs:194-200
requestb"digstore:req:v1"SHA-256(REQ_DST || u32be(len(method)) || method || store_id(32) || u64be(ts) || nonce(32)) → 32Bbls.rs:267-281
nodeb"digstore:node:v1"NODE_DST || program_hash(32) || public_output(32) || chia_header_hash(32) || u32be(height) || public_input (variable, NOT pre-hashed)bls.rs:209-224
attestb"digstore:attest:v1"ATTEST_DST || nonce(32) || store_id(32) || u64be(ts) (variable)bls.rs:232-243
tombb"digstore:tomb:v1"SHA-256(TOMB_DST || canonical(Tombstone)) → 32Bbls.rs:250-256
  • push authorizes the authenticated head; arg order is canonically (root, store_id).
  • request binds the method (a read-auth signature cannot replay as a write), the timestamp (freshness), and the nonce (uniqueness) — the §21.9 per-request auth.
  • attest is byte-identical to the guest's build_challenge (guest/attestation.rs:53-61).
NODE_DST and the merkle NODE_TAG share the same bytes

NODE_DST = b"digstore:node:v1" is the same byte string as the merkle NODE_TAG. The two are domain-distinct in practice because they are consumed in disjoint preimage shapes: a BLS node-attestation message versus a fixed 65-byte merkle pair.

Attestation verify (guest)

hash-to-curve DST = b"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_AUG_"   // guest/attestation.rs:20
aug_message = pubkey(48) || message
verify = pairing check e(pk, H(aug)) == e(g1, sig) // attestation.rs:63-94

Order: trusted-key membership → freshness (now − signed_time ≤ 300s AND now ≥ signed_time) → point-encoding validity → AugScheme pairing verify; any failure → the content path returns decoys (attestation.rs:97-122).