mirror of
https://github.com/luxfi/consensus.git
synced 2026-09-24 00:26:58 +00:00
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.
151 lines
4.4 KiB
Go
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[:]...)
|
|
}
|