Files
zeekay cdfb27641d threshold: Pulsar/Lens kernel adapters + corona KAT + Pulsar-SHA3 wiring
Squashed batch covering:

- protocols/pulsar/: lattice threshold lane (round-based wrapper for
  github.com/luxfi/pulsar kernel); doc.go documents Photon/Lumen/Beam/
  Pulsar/Pulse/Prism/Horizon/Quasar vocabulary stack from LP-105
- protocols/lens/: planned curve threshold sister kernel (design
  intent only; mirrors Pulsar shape; replaces stub LSS-FROST)

- protocols/lss/lss_pulsar.go + tests: LSS-Pulsar adapter
  (DynamicResharePulsar + PulsarSnapshotManager + BuildActivationTranscript).
  Now SHA3-suite-aware: PulsarConfig carries NebulaRoot, HashSuiteID,
  ImplementationVersion as optional transcript-binding fields that
  flow through to the activation message.

  10/10 acceptance tests pass:
    1. GroupKey preservation
    2. KeyEraID preservation
    3. Generation +1
    4. RollbackFrom=0 on forward
    5. t_old != t_new
    6. Disjoint set rotation
    7. Valid signature under unchanged GroupKey
    8. Pairwise material regenerated
    9. Rollback semantics
   10. Error surface
  Plus 1 new test for BuildActivationTranscript Nebula+suite fields.

- protocols/lss/lss_frost.go: marked DEPRECATED (placeholder Sign()/
  Refresh(), single-process simulation, hardcoded ChainKey/RID); will
  be replaced by lss_lens.go.

- corona KAT cross-oracle vs Go reference (N=16 deterministic seeds)
- corona+protocol+harness hardening against parallel-run flakes
- lss/adapters: drop NewXRPL/NewCardano panic; ship real XRPL address
  derivation
- corona SignWithConfig API expansion + test fixes
- v1.6.5 changelog
- go.sum h1 hashes for luxfi/log and 4 deps

Architecture (LP-105 / pulsar/DESIGN.md):
  LSS owns lifecycle (Generation, Rollback, snapshots, dealer/
  coordinator role separation). Pulsar owns lattice math (R_q shares,
  lattice Pedersen commits, Sign1/Sign2/Combine, Pulsar-SHA3 hash
  profile). The lss_pulsar adapter wires them. lss_lens (planned) does
  the same for curve math.

go.mod adds local replace github.com/luxfi/pulsar => ../pulsar.
2026-03-03 12:00:00 -08:00

420 lines
9.9 KiB
Go

// Package adapters - Sui blockchain adapter for Ed25519 threshold signatures
package adapters
import (
"encoding/binary"
"errors"
"fmt"
"github.com/luxfi/threshold/pkg/math/curve"
"golang.org/x/crypto/blake2b"
)
// SuiAdapter implements SignerAdapter for Sui blockchain
// Sui uses Ed25519 for signatures and BCS (Binary Canonical Serialization)
type SuiAdapter struct {
group curve.Curve
intentApp byte // 0x00 for transaction, 0x01 for personal message
}
// NewSuiAdapter creates a new Sui adapter
func NewSuiAdapter() *SuiAdapter {
return &SuiAdapter{
group: curve.Ed25519{},
intentApp: 0x00, // Transaction by default
}
}
// Digest computes Sui transaction digest using Blake2b
func (s *SuiAdapter) Digest(tx interface{}) ([]byte, error) {
switch v := tx.(type) {
case *SuiTransaction:
return s.digestTransaction(v)
case *SuiProgrammableTransaction:
return s.digestProgrammable(v)
case []byte:
// Raw BCS bytes
return s.hashWithIntent(v), nil
default:
return nil, fmt.Errorf("unsupported Sui transaction type: %T", tx)
}
}
// digestTransaction computes digest for Sui transaction
func (s *SuiAdapter) digestTransaction(tx *SuiTransaction) ([]byte, error) {
// Sui uses Intent || BCS(TransactionData) for signing
bcs := s.serializeToBCS(tx)
return s.hashWithIntent(bcs), nil
}
// digestProgrammable computes digest for programmable transaction
func (s *SuiAdapter) digestProgrammable(tx *SuiProgrammableTransaction) ([]byte, error) {
bcs := s.serializeProgrammableToBCS(tx)
return s.hashWithIntent(bcs), nil
}
// hashWithIntent computes Blake2b-256 hash with intent prefix
func (s *SuiAdapter) hashWithIntent(data []byte) []byte {
// Intent structure: [IntentScope(1) || IntentVersion(1) || IntentApp(1)]
intent := []byte{
0x00, // IntentScope: TransactionData
0x00, // IntentVersion: V0
s.intentApp,
}
h, _ := blake2b.New256(nil)
h.Write(intent)
h.Write(data)
return h.Sum(nil)
}
// SignEC creates Ed25519 partial signature for Sui
func (s *SuiAdapter) SignEC(digest []byte, share Share) (PartialSig, error) {
// This would integrate with FROST protocol for Ed25519
// For testing, provide a placeholder R value
z, err := coerceScalar(s.group, share.Value)
if err != nil {
return nil, fmt.Errorf("sui: %w", err)
}
return &EdDSAPartialSig{
PartyID: share.ID,
R: s.group.NewBasePoint(), // Placeholder for testing
Z: z,
}, nil
}
// AggregateEC combines Ed25519 partial signatures
func (s *SuiAdapter) AggregateEC(parts []PartialSig) (FullSig, error) {
if len(parts) == 0 {
return nil, errors.New("no partial signatures")
}
var r curve.Point
z := s.group.NewScalar()
expectedCurve := s.group.Name()
for i, part := range parts {
eddsaPart, ok := part.(*EdDSAPartialSig)
if !ok {
return nil, errors.New("invalid Ed25519 partial signature")
}
if eddsaPart.Z == nil {
return nil, fmt.Errorf("sui: ed25519 partial[%d] has nil scalar Z", i)
}
if got := eddsaPart.Z.Curve().Name(); got != expectedCurve {
return nil, fmt.Errorf("sui: ed25519 partial[%d] scalar Z on wrong curve (got %s, want %s)", i, got, expectedCurve)
}
if r == nil && eddsaPart.R != nil {
r = eddsaPart.R
}
z = z.Add(eddsaPart.Z)
}
return &EdDSAFullSig{
R: r,
Z: z,
}, nil
}
// Encode formats Ed25519 signature for Sui with flag byte
func (s *SuiAdapter) Encode(full FullSig) ([]byte, error) {
eddsaSig, ok := full.(*EdDSAFullSig)
if !ok {
return nil, errors.New("invalid Ed25519 signature")
}
// Sui signature format: Flag (1) || Signature (64) || PublicKey (32)
// Flag: 0x00 for Ed25519
sig := make([]byte, 97) // 1 + 64 + 32
sig[0] = 0x00 // Ed25519 flag
// Copy R (32 bytes)
rBytes, _ := eddsaSig.R.MarshalBinary()
// Skip format byte if present
if len(rBytes) == 33 && (rBytes[0] == 0x02 || rBytes[0] == 0x03 || rBytes[0] == 0x04) {
rBytes = rBytes[1:]
}
if len(rBytes) != 32 {
return nil, fmt.Errorf("invalid R length: %d", len(rBytes))
}
copy(sig[1:33], rBytes)
// Copy z (32 bytes)
zBytes, _ := eddsaSig.Z.MarshalBinary()
if len(zBytes) > 32 {
return nil, fmt.Errorf("invalid z length: %d", len(zBytes))
}
copy(sig[33+(32-len(zBytes)):65], zBytes)
// Note: Public key would be appended here in production
// For now, leaving zeros as placeholder
return sig[:65], nil // Return without pubkey for now
}
// ValidateConfig validates Sui-specific configuration
func (s *SuiAdapter) ValidateConfig(config *UnifiedConfig) error {
if config.SignatureScheme != SignatureEdDSA {
return errors.New("Sui requires Ed25519 signatures")
}
if _, ok := config.Group.(curve.Ed25519); !ok {
return errors.New("Sui requires Ed25519 curve")
}
return nil
}
// Sui-specific structures
type SuiTransaction struct {
Kind TransactionKind
Sender SuiAddress
GasPayment *GasPayment
GasPrice uint64
GasBudget uint64
Expiration *TransactionExpiration
}
type TransactionKind interface {
Type() TransactionType
}
type TransactionType byte
const (
TransferObject TransactionType = iota
Publish
MoveCall
TransferSui
ChangeEpoch
Pay
PaySui
PayAllSui
ProgrammableTransaction
)
type SuiAddress [32]byte
type GasPayment struct {
Objects []ObjectRef
Owner SuiAddress
Price uint64
Budget uint64
}
type ObjectRef struct {
ObjectID ObjectID
Version uint64
Digest ObjectDigest
}
type ObjectID [32]byte
type ObjectDigest [32]byte
type TransactionExpiration struct {
Epoch uint64
}
type SuiProgrammableTransaction struct {
Inputs []CallArg
Commands []Command
}
type CallArg interface {
Type() CallArgType
}
type CallArgType byte
const (
PureArg CallArgType = iota
ObjectArg
MoveVecArg
)
type Command interface {
Type() CommandType
}
type CommandType byte
const (
MoveCallCommand CommandType = iota
TransferObjectsCommand
SplitCoinsCommand
MergeCoinsCommand
PublishCommand
MakeMoveVecCommand
UpgradeCommand
)
// serializeToBCS serializes a Sui transaction to BCS format
func (s *SuiAdapter) serializeToBCS(tx *SuiTransaction) []byte {
// Simplified BCS serialization
// Actual implementation would follow Sui's BCS specification
var bcs []byte
// Transaction kind
bcs = append(bcs, byte(0)) // Placeholder for kind
// Sender address
bcs = append(bcs, tx.Sender[:]...)
// Gas payment
if tx.GasPayment != nil {
bcs = append(bcs, 1) // Has gas payment
bcs = append(bcs, s.serializeGasPayment(tx.GasPayment)...)
} else {
bcs = append(bcs, 0) // No gas payment
}
// Gas price
priceBytes := make([]byte, 8)
binary.LittleEndian.PutUint64(priceBytes, tx.GasPrice)
bcs = append(bcs, priceBytes...)
// Gas budget
budgetBytes := make([]byte, 8)
binary.LittleEndian.PutUint64(budgetBytes, tx.GasBudget)
bcs = append(bcs, budgetBytes...)
// Expiration
if tx.Expiration != nil {
bcs = append(bcs, 1) // Has expiration
epochBytes := make([]byte, 8)
binary.LittleEndian.PutUint64(epochBytes, tx.Expiration.Epoch)
bcs = append(bcs, epochBytes...)
} else {
bcs = append(bcs, 0) // No expiration
}
return bcs
}
// serializeProgrammableToBCS serializes programmable transaction
func (s *SuiAdapter) serializeProgrammableToBCS(tx *SuiProgrammableTransaction) []byte {
var bcs []byte
// Inputs count (ULEB128)
bcs = append(bcs, s.encodeULEB128(uint64(len(tx.Inputs)))...)
// Serialize inputs
for _, input := range tx.Inputs {
bcs = append(bcs, byte(input.Type()))
// Serialize input data (simplified)
}
// Commands count
bcs = append(bcs, s.encodeULEB128(uint64(len(tx.Commands)))...)
// Serialize commands
for _, cmd := range tx.Commands {
bcs = append(bcs, byte(cmd.Type()))
// Serialize command data (simplified)
}
return bcs
}
// serializeGasPayment serializes gas payment
func (s *SuiAdapter) serializeGasPayment(gas *GasPayment) []byte {
var data []byte
// Objects count
data = append(data, byte(len(gas.Objects)))
// Object refs
for _, obj := range gas.Objects {
data = append(data, obj.ObjectID[:]...)
versionBytes := make([]byte, 8)
binary.LittleEndian.PutUint64(versionBytes, obj.Version)
data = append(data, versionBytes...)
data = append(data, obj.Digest[:]...)
}
// Owner
data = append(data, gas.Owner[:]...)
// Price and budget
priceBytes := make([]byte, 8)
binary.LittleEndian.PutUint64(priceBytes, gas.Price)
data = append(data, priceBytes...)
budgetBytes := make([]byte, 8)
binary.LittleEndian.PutUint64(budgetBytes, gas.Budget)
data = append(data, budgetBytes...)
return data
}
// encodeULEB128 encodes unsigned integer in ULEB128 format
func (s *SuiAdapter) encodeULEB128(n uint64) []byte {
var result []byte
for {
b := byte(n & 0x7f)
n >>= 7
if n != 0 {
b |= 0x80
}
result = append(result, b)
if n == 0 {
break
}
}
return result
}
// GenerateSuiAddress generates a Sui address from public key
func (s *SuiAdapter) GenerateSuiAddress(publicKey [32]byte) SuiAddress {
// Sui address = Blake2b-256(flag || pubkey || 0x00)
// flag: 0x00 for Ed25519
h, _ := blake2b.New256(nil)
h.Write([]byte{0x00}) // Ed25519 flag
h.Write(publicKey[:])
h.Write([]byte{0x00}) // Single signature scheme
hash := h.Sum(nil)
var addr SuiAddress
copy(addr[:], hash)
return addr
}
// CreateTransferTransaction creates a simple transfer transaction
func (s *SuiAdapter) CreateTransferTransaction(from, to SuiAddress, amount uint64) *SuiTransaction {
return &SuiTransaction{
Sender: from,
GasPrice: 1000,
GasBudget: 10000000,
}
}
// EstimateGas estimates gas for Sui transaction
func (s *SuiAdapter) EstimateGas(tx *SuiTransaction) uint64 {
// Base computation cost
baseCost := uint64(1000)
// Transaction size cost
txSize := len(s.serializeToBCS(tx))
sizeCost := uint64(txSize * 10)
// Storage cost (if creating objects)
storageCost := uint64(0)
return baseCost + sizeCost + storageCost
}
// GetSuiConfig returns default Sui configuration
func GetDefaultSuiConfig() map[string]interface{} {
return map[string]interface{}{
"signature_type": SignatureEdDSA,
"curve": "Ed25519",
"hash_algorithm": "Blake2b-256",
"serialization": "BCS",
"intent_version": 0,
}
}