mirror of
https://github.com/lux-cpp/chains.git
synced 2026-10-30 18:14:54 +00:00
These were in luxfi/compute, a private repo whose licence is the same BSD-3-Clause as luxfi/node's. Nothing here needed protecting, and keeping it private broke the public C++ chain builds that reference it. Sibling paths lose a level: the chains sat three deep in compute and sit one deep here, so what reached ~/work through ../../../../ now does it through ../../../. Co-authored-by: Hanzo Dev <[email protected]>
464 lines
15 KiB
Cheetah
464 lines
15 KiB
Cheetah
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
|
|
// SPDX-License-Identifier: BSD-3-Clause-Eco
|
|
//
|
|
// Generator for the C++ port's cross-language golden header. Run:
|
|
//
|
|
// go test ./fhevm -run TestGenerateCppGolden -v > /tmp/golden.txt
|
|
//
|
|
// It writes chains/cpp/fhevm/test/golden.hpp in node2. Nothing in the Go chain
|
|
// depends on it; it exists so the C++ port is checked against THIS package's
|
|
// own bytes rather than against a second reading of the spec.
|
|
|
|
package fhevm
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"sort"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/luxfi/chains/fee"
|
|
"github.com/luxfi/chains/mpcvm/fhe"
|
|
"github.com/luxfi/crypto/mldsa"
|
|
"github.com/luxfi/ids"
|
|
)
|
|
|
|
// detRand is a deterministic byte source, so the golden keys are the same on
|
|
// every run and the header is stable in git.
|
|
type detRand struct {
|
|
state [32]byte
|
|
buf []byte
|
|
}
|
|
|
|
func newDetRand(seed string) *detRand {
|
|
return &detRand{state: sha256.Sum256([]byte(seed))}
|
|
}
|
|
|
|
func (r *detRand) Read(p []byte) (int, error) {
|
|
for i := range p {
|
|
if len(r.buf) == 0 {
|
|
r.state = sha256.Sum256(r.state[:])
|
|
r.buf = append([]byte(nil), r.state[:]...)
|
|
}
|
|
p[i] = r.buf[0]
|
|
r.buf = r.buf[1:]
|
|
}
|
|
return len(p), nil
|
|
}
|
|
|
|
type goldKey struct {
|
|
priv *mldsa.PrivateKey
|
|
pub []byte
|
|
addr fee.Account
|
|
}
|
|
|
|
func genKey(t *testing.T, seed string) goldKey {
|
|
t.Helper()
|
|
priv, err := mldsa.GenerateKey(newDetRand(seed), mldsa.MLDSA65)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
pub := priv.PublicKey.Bytes()
|
|
return goldKey{priv: priv, pub: pub, addr: addressOf(pub)}
|
|
}
|
|
|
|
func (k goldKey) signDet(t *testing.T, tx *Transaction, chain ids.ID) {
|
|
t.Helper()
|
|
tx.Auth = k.pub
|
|
sig, err := k.priv.SignCtxDeterministic(tx.SigningBytes(chain), nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
tx.Sig = sig
|
|
tx.id = ids.Empty
|
|
}
|
|
|
|
type emitter struct{ w io.Writer }
|
|
|
|
func (e emitter) line(format string, a ...any) {
|
|
fmt.Fprintf(e.w, format+"\n", a...)
|
|
}
|
|
|
|
func (e emitter) hexConst(name string, b []byte) {
|
|
e.line("inline constexpr const char* %s = \"%s\";", name, hex.EncodeToString(b))
|
|
}
|
|
|
|
func (e emitter) strConst(name, s string) {
|
|
e.line("inline constexpr const char* %s = %s;", name, cquote(s))
|
|
}
|
|
|
|
func cquote(s string) string {
|
|
var b strings.Builder
|
|
b.WriteByte('"')
|
|
for i := 0; i < len(s); i++ {
|
|
c := s[i]
|
|
switch {
|
|
case c == '"':
|
|
b.WriteString("\\\"")
|
|
case c == '\\':
|
|
b.WriteString("\\\\")
|
|
case c < 0x20 || c > 0x7e:
|
|
fmt.Fprintf(&b, "\\x%02x", c)
|
|
default:
|
|
b.WriteByte(c)
|
|
}
|
|
}
|
|
b.WriteByte('"')
|
|
return b.String()
|
|
}
|
|
|
|
// goldChainID is the chain every golden transaction is signed for.
|
|
var goldChainID = ids.ID{'f', 'c', 'h', 'a', 'i', 'n', '-', 't', 'e', 's', 't'}
|
|
|
|
const goldGenesisTime = int64(1_700_000_000)
|
|
|
|
func TestGenerateCppGolden(t *testing.T) {
|
|
out := os.Getenv("FHEVM_CPP_GOLDEN")
|
|
if out == "" {
|
|
t.Skip("set FHEVM_CPP_GOLDEN=<path to chains/cpp/fhevm/test/golden.hpp> to regenerate")
|
|
}
|
|
f, err := os.Create(out)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer f.Close()
|
|
e := emitter{w: f}
|
|
|
|
e.line("// Copyright (C) 2026, Lux Industries Inc. All rights reserved.")
|
|
e.line("// SPDX-License-Identifier: BSD-3-Clause-Eco")
|
|
e.line("//")
|
|
e.line("// GENERATED by chains/fhevm/cppgolden_test.go in the Go F-Chain. Do not edit.")
|
|
e.line("// Every value here came out of the Go implementation this port renders, so a")
|
|
e.line("// C++ test that reproduces one is agreeing with Go and not with itself.")
|
|
e.line("//")
|
|
e.line("// Regenerate: copy test/golden_gen_test.go into ~/work/lux/chains/fhevm as")
|
|
e.line("// cppgolden_test.go, then")
|
|
e.line("// FHEVM_CPP_GOLDEN=<abs path to this file> go test ./fhevm -run TestGenerateCppGolden")
|
|
e.line("")
|
|
e.line("#pragma once")
|
|
e.line("")
|
|
e.line("namespace fhevm_golden {")
|
|
e.line("")
|
|
|
|
e.hexConst("kChainId", goldChainID[:])
|
|
e.line("inline constexpr long long kGenesisTime = %d;", goldGenesisTime)
|
|
e.line("")
|
|
|
|
// ---- identities -------------------------------------------------------
|
|
payer := genKey(t, "fhevm-golden-payer")
|
|
grantee := genKey(t, "fhevm-golden-grantee")
|
|
m0 := genKey(t, "fhevm-golden-member-0")
|
|
m1 := genKey(t, "fhevm-golden-member-1")
|
|
m2 := genKey(t, "fhevm-golden-member-2")
|
|
|
|
e.line("// ---- identities: ML-DSA-65 public keys and the addresses they derive ----")
|
|
for _, kv := range []struct {
|
|
name string
|
|
k goldKey
|
|
}{
|
|
{"Payer", payer}, {"Grantee", grantee},
|
|
{"M0", m0}, {"M1", m1}, {"M2", m2},
|
|
} {
|
|
e.hexConst("k"+kv.name+"Pub", kv.k.pub)
|
|
e.hexConst("k"+kv.name+"Addr", kv.k.addr[:])
|
|
}
|
|
e.line("")
|
|
|
|
// ---- committee --------------------------------------------------------
|
|
type pair struct {
|
|
m fhe.CommitteeMember
|
|
k goldKey
|
|
}
|
|
pairs := []pair{
|
|
{fhe.CommitteeMember{NodeID: ids.NodeID{1}, PublicKey: m0.pub, Weight: 1}, m0},
|
|
{fhe.CommitteeMember{NodeID: ids.NodeID{2}, PublicKey: m1.pub, Weight: 1}, m1},
|
|
{fhe.CommitteeMember{NodeID: ids.NodeID{3}, PublicKey: m2.pub, Weight: 1}, m2},
|
|
}
|
|
sort.Slice(pairs, func(i, j int) bool { return pairs[i].m.NodeID.Compare(pairs[j].m.NodeID) < 0 })
|
|
committee := make([]fhe.CommitteeMember, len(pairs))
|
|
keys := make([]goldKey, len(pairs))
|
|
for i, p := range pairs {
|
|
p.m.Index = i
|
|
committee[i] = p.m
|
|
keys[i] = p.k
|
|
}
|
|
networkPK := []byte("network-fhe-public-key")
|
|
|
|
e.line("// ---- committee: node ids, the digest members attest, and its JSON ----")
|
|
for i, m := range committee {
|
|
e.hexConst(fmt.Sprintf("kMember%dNodeId", i), m.NodeID[:])
|
|
e.strConst(fmt.Sprintf("kMember%dNodeIdString", i), m.NodeID.String())
|
|
}
|
|
e.hexConst("kNetworkPublicKey", networkPK)
|
|
cd0 := committeeDigest(0, 2, networkPK, committee)
|
|
cd1 := committeeDigest(1, 2, networkPK, committee)
|
|
e.hexConst("kCommitteeDigestEpoch0T2", cd0[:])
|
|
e.hexConst("kCommitteeDigestEpoch1T2", cd1[:])
|
|
cdEmpty := committeeDigest(7, 3, nil, nil)
|
|
e.hexConst("kCommitteeDigestEmpty", cdEmpty[:])
|
|
e.line("")
|
|
|
|
// ---- derivations ------------------------------------------------------
|
|
e.line("// ---- content derivations ----")
|
|
dA := sha256.Sum256([]byte("a"))
|
|
e.hexConst("kDigestA", dA[:])
|
|
for _, scheme := range []string{"", "ckks-n14", "tfhe-n10"} {
|
|
h := deriveHandle(dA, scheme)
|
|
e.hexConst("kHandleA_"+cname(scheme), h[:])
|
|
}
|
|
pid := derivePermitID(deriveHandle(dA, "ckks-n14"), payer.addr, grantee.addr, 1, 0, 1)
|
|
e.hexConst("kPermitIdA", pid[:])
|
|
pid2 := derivePermitID(deriveHandle(dA, "ckks-n14"), payer.addr, grantee.addr, 5, 1234567, 9)
|
|
e.hexConst("kPermitIdB", pid2[:])
|
|
rid := deriveRequestID(deriveHandle(dA, "ckks-n14"), grantee.addr, 2)
|
|
e.hexConst("kRequestIdA", rid[:])
|
|
e.line("")
|
|
|
|
// ---- payload JSON -----------------------------------------------------
|
|
e.line("// ---- payload encodings, exactly as encoding/json writes them ----")
|
|
regPayload := mustMarshal(t, RegisterPayload{Digest: dA, Type: 4, Level: 3, Size: 4096})
|
|
e.strConst("kRegisterPayload", string(regPayload))
|
|
grantPayload := mustMarshal(t, GrantPayload{Grantee: grantee.addr, Operations: fhe.PermitOpDecrypt, Expiry: 0})
|
|
e.strConst("kGrantPayload", string(grantPayload))
|
|
revokePayload := mustMarshal(t, RevokePayload{Reason: "no longer sanctioned"})
|
|
e.strConst("kRevokePayload", string(revokePayload))
|
|
reqPayload := mustMarshal(t, RequestPayload{
|
|
PermitID: pid, Callback: [20]byte{0xca, 0x11}, Selector: [4]byte{1, 2, 3, 4}, Expiry: 0,
|
|
})
|
|
e.strConst("kRequestPayload", string(reqPayload))
|
|
resultH := sha256.Sum256([]byte("plaintext-handle"))
|
|
e.hexConst("kResultHandle", resultH[:])
|
|
fulfillPayload := mustMarshal(t, FulfillPayload{Result: resultH})
|
|
e.strConst("kFulfillPayload", string(fulfillPayload))
|
|
advPayload := mustMarshal(t, AdvancePayload{
|
|
Epoch: 1, Committee: committee, Threshold: 2, PublicKey: networkPK,
|
|
})
|
|
e.strConst("kAdvancePayload", string(advPayload))
|
|
e.line("")
|
|
|
|
// ---- transactions -----------------------------------------------------
|
|
e.line("// ---- signed transactions: content, preimage, wire, id, effect, gas ----")
|
|
handle := deriveHandle(dA, "ckks-n14")
|
|
|
|
reg := &Transaction{
|
|
Type: TxRegisterCiphertext, Scheme: "ckks-n14", Payer: payer.addr,
|
|
Subject: handle, GasLimit: 500_000, Nonce: 1, Payload: regPayload,
|
|
}
|
|
payer.signDet(t, reg, goldChainID)
|
|
emitTx(t, e, "Register", reg)
|
|
|
|
grant := &Transaction{
|
|
Type: TxGrantPermit, Payer: payer.addr, Subject: handle,
|
|
GasLimit: 500_000, Nonce: 2, Payload: grantPayload,
|
|
}
|
|
payer.signDet(t, grant, goldChainID)
|
|
emitTx(t, e, "Grant", grant)
|
|
|
|
revoke := &Transaction{
|
|
Type: TxRevokePermit, Payer: payer.addr, Subject: pid,
|
|
GasLimit: 500_000, Nonce: 3, Payload: revokePayload,
|
|
}
|
|
payer.signDet(t, revoke, goldChainID)
|
|
emitTx(t, e, "Revoke", revoke)
|
|
|
|
request := &Transaction{
|
|
Type: TxRequestDecrypt, Scheme: "ckks-n14", Payer: grantee.addr, Subject: handle,
|
|
GasLimit: 500_000, Nonce: 1, Payload: reqPayload,
|
|
}
|
|
grantee.signDet(t, request, goldChainID)
|
|
emitTx(t, e, "Request", request)
|
|
|
|
reqID := deriveRequestID(handle, grantee.addr, 1)
|
|
e.hexConst("kRequestIdOfRequestTx", reqID[:])
|
|
fulfill := &Transaction{
|
|
Type: TxFulfillDecrypt, Payer: keys[0].addr, Subject: reqID,
|
|
GasLimit: 500_000, Nonce: 1, Payload: fulfillPayload,
|
|
}
|
|
keys[0].signDet(t, fulfill, goldChainID)
|
|
emitTx(t, e, "Fulfill", fulfill)
|
|
|
|
advance := &Transaction{
|
|
Type: TxAdvanceEpoch, Payer: keys[0].addr, Subject: cd1,
|
|
GasLimit: 500_000, Nonce: 2, Payload: advPayload,
|
|
}
|
|
keys[0].signDet(t, advance, goldChainID)
|
|
emitTx(t, e, "Advance", advance)
|
|
|
|
// A transaction signed for ANOTHER chain: the C++ side must refuse it.
|
|
other := ids.ID{'o', 't', 'h', 'e', 'r'}
|
|
foreign := &Transaction{
|
|
Type: TxRegisterCiphertext, Scheme: "ckks-n14", Payer: payer.addr,
|
|
Subject: handle, GasLimit: 500_000, Nonce: 1, Payload: regPayload,
|
|
}
|
|
payer.signDet(t, foreign, other)
|
|
e.hexConst("kForeignChainId", other[:])
|
|
e.hexConst("kTxForeignBytes", foreign.Bytes())
|
|
e.line("")
|
|
|
|
// ---- block ------------------------------------------------------------
|
|
e.line("// ---- a block over two of those transactions ----")
|
|
parent := ids.ID{0xaa, 0xbb}
|
|
vm := &VM{chainID: goldChainID}
|
|
blk := &Block{
|
|
parentID: parent, height: 7, timestamp: time.Unix(1_700_000_123, 0),
|
|
transactions: []*Transaction{reg, grant}, vm: vm,
|
|
}
|
|
blk.id = blk.computeID()
|
|
e.hexConst("kBlockParent", parent[:])
|
|
e.line("inline constexpr unsigned long long kBlockHeight = %d;", blk.height)
|
|
e.line("inline constexpr long long kBlockTime = %d;", blk.timestamp.Unix())
|
|
e.hexConst("kBlockBytes", blk.Bytes())
|
|
e.hexConst("kBlockId", blk.id[:])
|
|
|
|
empty := &Block{vm: vm}
|
|
e.line("inline constexpr int kEmptyBlockSize = %d;", len(empty.Bytes()))
|
|
e.line("inline constexpr int kTxEntry = %d;", txEntry)
|
|
e.line("")
|
|
|
|
// ---- gas --------------------------------------------------------------
|
|
e.line("// ---- the gas schedule, priced per (operation, scheme) ----")
|
|
e.line("inline constexpr unsigned long long kGasPrice = %d;", uint64(GasPrice))
|
|
e.line("inline constexpr unsigned long long kGasPerByte = %d;", uint64(GasPerByte))
|
|
e.line("inline constexpr unsigned long long kMinScheduledFee = %d;", MinScheduledFee())
|
|
e.line("struct GasCase { unsigned char type; const char* scheme; int payload_len; unsigned long long gas; unsigned long long fee; };")
|
|
e.line("inline constexpr GasCase kGasCases[] = {")
|
|
schemes := make([]string, 0, len(schemeGas))
|
|
for s := range schemeGas {
|
|
schemes = append(schemes, s)
|
|
}
|
|
sort.Strings(schemes)
|
|
for _, op := range []uint8{
|
|
TxRegisterCiphertext, TxGrantPermit, TxRevokePermit,
|
|
TxRequestDecrypt, TxFulfillDecrypt, TxAdvanceEpoch,
|
|
} {
|
|
for _, pl := range []int{0, 7, 130} {
|
|
payload := make([]byte, pl)
|
|
if usesScheme(op) {
|
|
for _, s := range schemes {
|
|
g, err := GasFor(&Transaction{Type: op, Scheme: s, Payload: payload})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
fv, _ := fee.Cost(g, GasPrice)
|
|
e.line(" {%d, %q, %d, %d, %d},", op, s, pl, uint64(g), fv)
|
|
}
|
|
continue
|
|
}
|
|
g, err := GasFor(&Transaction{Type: op, Payload: payload})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
fv, _ := fee.Cost(g, GasPrice)
|
|
e.line(" {%d, \"\", %d, %d, %d},", op, pl, uint64(g), fv)
|
|
}
|
|
}
|
|
e.line("};")
|
|
e.line("inline constexpr const char* kSchemes[] = {")
|
|
for _, s := range schemes {
|
|
e.line(" %q,", s)
|
|
}
|
|
e.line("};")
|
|
e.line("")
|
|
|
|
// ---- records ----------------------------------------------------------
|
|
e.line("// ---- the four persisted records, as the Go chain writes them ----")
|
|
ctRec := &CiphertextRecord{
|
|
CiphertextMeta: fhe.CiphertextMeta{
|
|
Handle: handle, Owner: payer.addr, Type: 4, Level: 3, Epoch: 0,
|
|
RegisteredAt: goldGenesisTime, Size: 4096, ChainID: goldChainID,
|
|
},
|
|
Scheme: "ckks-n14", Digest: dA,
|
|
}
|
|
e.strConst("kCiphertextRecordJSON", string(mustMarshal(t, ctRec)))
|
|
|
|
pmRec := &PermitRecord{
|
|
Permit: fhe.Permit{
|
|
PermitID: pid, Handle: handle, Grantee: grantee.addr, Grantor: payer.addr,
|
|
Operations: fhe.PermitOpDecrypt, Expiry: 0, CreatedAt: goldGenesisTime,
|
|
ChainID: goldChainID,
|
|
},
|
|
Status: StatusActive,
|
|
}
|
|
e.strConst("kPermitRecordJSON", string(mustMarshal(t, pmRec)))
|
|
|
|
drRec := &DecryptRecord{
|
|
DecryptRequest: fhe.DecryptRequest{
|
|
RequestID: reqID, CiphertextHandle: handle, Requester: grantee.addr,
|
|
Callback: [20]byte{0xca, 0x11}, CallbackSelector: [4]byte{1, 2, 3, 4},
|
|
SourceChain: goldChainID, Epoch: 0, Nonce: 1,
|
|
Expiry: goldGenesisTime + DefaultRequestWindow, Status: fhe.RequestPending,
|
|
CreatedAt: goldGenesisTime,
|
|
},
|
|
PermitID: pid,
|
|
Attestations: []Attestation{{Member: keys[0].addr, Value: resultH}},
|
|
}
|
|
e.strConst("kDecryptRecordJSON", string(mustMarshal(t, drRec)))
|
|
|
|
epRec := &EpochRecord{EpochInfo: fhe.EpochInfo{
|
|
Epoch: 0, StartTime: goldGenesisTime, Committee: committee,
|
|
Threshold: 2, PublicKey: networkPK, Status: fhe.EpochActive,
|
|
}}
|
|
e.strConst("kEpochRecordJSON", string(mustMarshal(t, epRec)))
|
|
e.line("")
|
|
|
|
// ---- genesis ----------------------------------------------------------
|
|
e.line("// ---- the genesis document a node is booted with, and the block it names ----")
|
|
g := Genesis{
|
|
Version: 1, Timestamp: goldGenesisTime,
|
|
Alloc: map[string]uint64{hex.EncodeToString(payer.addr[:]): 10_000_000_000},
|
|
Committee: committee, Threshold: 2, PublicKey: networkPK,
|
|
}
|
|
e.strConst("kGenesisJSON", string(mustMarshal(t, &g)))
|
|
gb := &Block{id: ids.Empty, parentID: ids.Empty, height: 0,
|
|
timestamp: time.Unix(goldGenesisTime, 0), vm: vm}
|
|
gid := gb.computeID()
|
|
e.hexConst("kGenesisBlockId", gid[:])
|
|
e.line("")
|
|
|
|
e.line("} // namespace fhevm_golden")
|
|
t.Logf("wrote %s", out)
|
|
}
|
|
|
|
func emitTx(t *testing.T, e emitter, name string, tx *Transaction) {
|
|
t.Helper()
|
|
e.hexConst("kTx"+name+"Content", tx.content())
|
|
e.hexConst("kTx"+name+"Signing", tx.SigningBytes(goldChainID))
|
|
e.hexConst("kTx"+name+"Bytes", tx.Bytes())
|
|
id := tx.ID()
|
|
e.hexConst("kTx"+name+"Id", id[:])
|
|
eff := tx.effect()
|
|
e.hexConst("kTx"+name+"Effect", eff[:])
|
|
g, err := GasFor(tx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
f, err := FeeFor(tx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
e.line("inline constexpr unsigned long long kTx%sGas = %d;", name, uint64(g))
|
|
e.line("inline constexpr unsigned long long kTx%sFee = %d;", name, f)
|
|
}
|
|
|
|
func mustMarshal(t *testing.T, v any) []byte {
|
|
t.Helper()
|
|
b, err := json.Marshal(v)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return b
|
|
}
|
|
|
|
func cname(s string) string {
|
|
if s == "" {
|
|
return "NoScheme"
|
|
}
|
|
return strings.NewReplacer("-", "_").Replace(s)
|
|
}
|