Files
consensus/protocol/auth/hash.go
T
zeekayandGitHub 727a87d4a2 consensus: protocol/auth — TxAuthEnvelope + PQPermit + ContractAuthProfile (#2)
Adds the canonical PQ authorization surface for the user-facing layers:
  - protocol/auth/tx_envelope.go  — TxAuthEnvelope + SigningDigest +
    VerifyTxAuthEnvelope; binds wallet scheme, hash suite, account ID,
    nonce, expiry, fee policy, call root via TupleHash256
    "LUX_TX_AUTH_V1"
  - protocol/auth/permit.go        — PQPermit (replaces EIP-2612) with
    TupleHash256 "LUX_PQ_PERMIT_V1"
  - protocol/auth/contract_profile.go — per-contract auth declaration
    (admin / upgrade / pause / permit scheme)
  - protocol/auth/account_id.go    — SHAKE256-384 PQ AccountID derivation
  - protocol/auth/precompile.go    — EVM precompile contracts for native
    PQ verify (wiring into coreth is a follow-up)

Local WalletSchemeID / TxSchemeID / ContractAuthID / KeyExchangeID /
RecoverySchemeID declared as temporary types in protocol/auth/scheme_ids.go;
the parallel ChainSecurityProfile expansion agent's canonical types in
config/ will replace them via type aliases on merge.

Patch-bump.
2026-05-10 21:31:51 -07:00

151 lines
4.4 KiB
Go

// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package auth
import (
"encoding/binary"
"golang.org/x/crypto/sha3"
)
// hash.go — SP 800-185 TupleHash256 + SHAKE256 helpers used by every
// auth digest in this package. Mirrors the same primitives in
// protocol/zchain/hash.go and protocol/quasar/round_digest.go; vendored
// here so the auth package has no upward dependency on zchain.
//
// Why TupleHash and not plain SHAKE: TupleHash gives unambiguous length
// framing per part, so (parts ["ab", "cd"]) and (parts ["a", "bcd"])
// hash to distinct values. Injectivity is what makes flipping any
// single field's bytes change the digest, even if a neighbouring field
// could absorb the flipped bytes.
//
// Output width here is 48 bytes (384 bits) — the canonical PQ-aligned
// digest width used across zchain, quasar, and auth, matching SHA3-384
// and the 48-byte PQ AccountID width.
// tupleHash256 computes TupleHash256(parts, outLen, customization) per
// NIST SP 800-185 §5. Caller owns the encoding of each part.
func tupleHash256(parts [][]byte, outLen int, customization string) []byte {
var x []byte
for _, p := range parts {
x = append(x, encodeStringSP800185(p)...)
}
x = append(x, rightEncodeSP800185(uint64(outLen)*8)...)
h := sha3.NewCShake256([]byte("TupleHash"), []byte(customization))
_, _ = h.Write(x)
out := make([]byte, outLen)
_, _ = h.Read(out)
return out
}
// tupleHash48 is the 48-byte (384-bit) wrapper. Every auth digest uses
// it; the output width is the canonical 48-byte digest used elsewhere
// in this consensus tree.
func tupleHash48(parts [][]byte, customization string) [48]byte {
out := tupleHash256(parts, 48, customization)
var d [48]byte
copy(d[:], out)
return d
}
// shake256_384 returns SHAKE256(input) squeezed to 48 bytes. Used by
// DeriveAccountID to bind a deterministic 48-byte AccountID to (chain,
// scheme, pubkey). SHAKE256 (FIPS 202) is the canonical PQ-aligned XOF
// and lives on the same SP 800-185 family as TupleHash256, so the auth
// package's hash surface is one family.
func shake256_384(input []byte, customization string) [48]byte {
h := sha3.NewCShake256(nil, []byte(customization))
_, _ = h.Write(input)
var out [48]byte
_, _ = h.Read(out[:])
return out
}
// leftEncodeSP800185 returns the SP 800-185 §2.3 left_encode(x) byte
// string. Operates on BIT length; every caller multiplies by 8 first.
func leftEncodeSP800185(x uint64) []byte {
if x == 0 {
return []byte{0x01, 0x00}
}
var buf [8]byte
binary.BigEndian.PutUint64(buf[:], x)
i := 0
for i < 7 && buf[i] == 0 {
i++
}
out := make([]byte, 0, 9-i)
out = append(out, byte(8-i))
out = append(out, buf[i:]...)
return out
}
// rightEncodeSP800185 returns the SP 800-185 §2.3 right_encode(x) byte
// string. Used at the tail of TupleHash to bind output length into
// the absorbed stream.
func rightEncodeSP800185(x uint64) []byte {
if x == 0 {
return []byte{0x00, 0x01}
}
var buf [8]byte
binary.BigEndian.PutUint64(buf[:], x)
i := 0
for i < 7 && buf[i] == 0 {
i++
}
out := make([]byte, 0, 9-i)
out = append(out, buf[i:]...)
out = append(out, byte(8-i))
return out
}
// encodeStringSP800185 returns left_encode(bit_len(s)) || s per
// SP 800-185 §2.3.
func encodeStringSP800185(s []byte) []byte {
out := leftEncodeSP800185(uint64(len(s)) * 8)
out = append(out, s...)
return out
}
// u16BE returns v as a fresh 2-byte big-endian slice.
func u16BE(v uint16) []byte {
var x [2]byte
binary.BigEndian.PutUint16(x[:], v)
return append([]byte(nil), x[:]...)
}
// u32BE returns v as a fresh 4-byte big-endian slice.
func u32BE(v uint32) []byte {
var x [4]byte
binary.BigEndian.PutUint32(x[:], v)
return append([]byte(nil), x[:]...)
}
// u64BE returns v as a fresh 8-byte big-endian slice.
func u64BE(v uint64) []byte {
var x [8]byte
binary.BigEndian.PutUint64(x[:], v)
return append([]byte(nil), x[:]...)
}
// appendU16 / appendU32 / appendU64 are the in-place writers used by
// the Marshal codecs in this package.
func appendU16(b []byte, v uint16) []byte {
var x [2]byte
binary.BigEndian.PutUint16(x[:], v)
return append(b, x[:]...)
}
func appendU32(b []byte, v uint32) []byte {
var x [4]byte
binary.BigEndian.PutUint32(x[:], v)
return append(b, x[:]...)
}
func appendU64(b []byte, v uint64) []byte {
var x [8]byte
binary.BigEndian.PutUint64(x[:], v)
return append(b, x[:]...)
}