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

409 lines
9.8 KiB
Go

// Package adapters - NEAR blockchain adapter for Ed25519 threshold signatures
package adapters
import (
"encoding/binary"
"errors"
"fmt"
"github.com/luxfi/threshold/pkg/math/curve"
"golang.org/x/crypto/sha3"
)
// NEARAdapter implements SignerAdapter for NEAR Protocol
// NEAR uses Ed25519 for signatures and Borsh serialization
type NEARAdapter struct {
group curve.Curve
networkID string // "mainnet", "testnet", or custom
}
// NewNEARAdapter creates a new NEAR adapter
func NewNEARAdapter(networkID string) *NEARAdapter {
if networkID == "" {
networkID = "mainnet"
}
return &NEARAdapter{
group: curve.Ed25519{},
networkID: networkID,
}
}
// Digest computes NEAR transaction digest
func (n *NEARAdapter) Digest(tx interface{}) ([]byte, error) {
switch v := tx.(type) {
case *NEARTransaction:
return n.digestTransaction(v)
case *NEARAction:
return n.digestAction(v)
case []byte:
// Raw Borsh bytes
return n.hashBorsh(v), nil
default:
return nil, fmt.Errorf("unsupported NEAR transaction type: %T", tx)
}
}
// digestTransaction computes digest for NEAR transaction
func (n *NEARAdapter) digestTransaction(tx *NEARTransaction) ([]byte, error) {
// NEAR uses Borsh serialization with SHA-256
borsh := n.serializeToBorsh(tx)
return n.hashBorsh(borsh), nil
}
// digestAction computes digest for NEAR action
func (n *NEARAdapter) digestAction(action *NEARAction) ([]byte, error) {
borsh := n.serializeActionToBorsh(*action)
return n.hashBorsh(borsh), nil
}
// hashBorsh computes SHA-256 hash of Borsh data
func (n *NEARAdapter) hashBorsh(data []byte) []byte {
h := sha3.New256()
h.Write(data)
return h.Sum(nil)
}
// SignEC creates Ed25519 partial signature for NEAR
func (n *NEARAdapter) 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(n.group, share.Value)
if err != nil {
return nil, fmt.Errorf("near: %w", err)
}
return &EdDSAPartialSig{
PartyID: share.ID,
R: n.group.NewBasePoint(), // Placeholder for testing
Z: z,
}, nil
}
// AggregateEC combines Ed25519 partial signatures
func (n *NEARAdapter) AggregateEC(parts []PartialSig) (FullSig, error) {
if len(parts) == 0 {
return nil, errors.New("no partial signatures")
}
var r curve.Point
z := n.group.NewScalar()
expectedCurve := n.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("near: ed25519 partial[%d] has nil scalar Z", i)
}
if got := eddsaPart.Z.Curve().Name(); got != expectedCurve {
return nil, fmt.Errorf("near: 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 NEAR
func (n *NEARAdapter) Encode(full FullSig) ([]byte, error) {
eddsaSig, ok := full.(*EdDSAFullSig)
if !ok {
return nil, errors.New("invalid Ed25519 signature")
}
// Ed25519 signature: R (32 bytes) || z (32 bytes)
sig := make([]byte, 64)
// Copy R
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[:32], rBytes)
// Copy z
zBytes, _ := eddsaSig.Z.MarshalBinary()
if len(zBytes) > 32 {
return nil, fmt.Errorf("invalid z length: %d", len(zBytes))
}
copy(sig[32+(32-len(zBytes)):], zBytes)
return sig, nil
}
// ValidateConfig validates NEAR-specific configuration
func (n *NEARAdapter) ValidateConfig(config *UnifiedConfig) error {
if config.SignatureScheme != SignatureEdDSA {
return errors.New("NEAR requires Ed25519 signatures")
}
if _, ok := config.Group.(curve.Ed25519); !ok {
return errors.New("NEAR requires Ed25519 curve")
}
return nil
}
// NEAR-specific structures
type NEARTransaction struct {
SignerID string
PublicKey PublicKey
Nonce uint64
ReceiverID string
BlockHash [32]byte
Actions []NEARAction
}
type PublicKey struct {
KeyType KeyType
Data [32]byte
}
type KeyType byte
const (
ED25519KeyType KeyType = 0
SECP256K1KeyType KeyType = 1
)
type NEARAction interface {
Type() ActionType
}
type ActionType byte
const (
CreateAccount ActionType = iota
DeployContract
FunctionCall
Transfer
Stake
AddKey
DeleteKey
DeleteAccount
)
type TransferAction struct {
Deposit NEARAmount
}
func (t *TransferAction) Type() ActionType { return Transfer }
type FunctionCallAction struct {
MethodName string
Args []byte
Gas uint64
Deposit NEARAmount
}
func (f *FunctionCallAction) Type() ActionType { return FunctionCall }
type DeployContractAction struct {
Code []byte
}
func (d *DeployContractAction) Type() ActionType { return DeployContract }
type StakeAction struct {
Stake NEARAmount
PublicKey PublicKey
}
func (s *StakeAction) Type() ActionType { return Stake }
type AddKeyAction struct {
PublicKey PublicKey
AccessKey AccessKey
}
func (a *AddKeyAction) Type() ActionType { return AddKey }
type AccessKey struct {
Nonce uint64
Permission Permission
}
type Permission interface {
Type() PermissionType
}
type PermissionType byte
const (
FullAccess PermissionType = iota
FunctionCallPermission
)
// FullAccessPermission represents full access
type FullAccessPermission struct{}
func (f *FullAccessPermission) Type() PermissionType { return FullAccess }
// FunctionCallPermissionData represents function call permission
type FunctionCallPermissionData struct {
Allowance *NEARAmount
ReceiverID string
MethodNames []string
}
func (f *FunctionCallPermissionData) Type() PermissionType { return FunctionCallPermission }
type NEARAmount struct {
Amount [16]byte // 128-bit integer
}
// serializeToBorsh serializes a NEAR transaction to Borsh format
func (n *NEARAdapter) serializeToBorsh(tx *NEARTransaction) []byte {
// Simplified Borsh serialization
// Actual implementation would follow NEAR's Borsh specification
var borsh []byte
// Signer ID (string length + data)
signerBytes := []byte(tx.SignerID)
lenBytes := make([]byte, 4)
binary.LittleEndian.PutUint32(lenBytes, uint32(len(signerBytes)))
borsh = append(borsh, lenBytes...)
borsh = append(borsh, signerBytes...)
// Public key
borsh = append(borsh, byte(tx.PublicKey.KeyType))
borsh = append(borsh, tx.PublicKey.Data[:]...)
// Nonce
nonceBytes := make([]byte, 8)
binary.LittleEndian.PutUint64(nonceBytes, tx.Nonce)
borsh = append(borsh, nonceBytes...)
// Receiver ID
receiverBytes := []byte(tx.ReceiverID)
lenBytes = make([]byte, 4)
binary.LittleEndian.PutUint32(lenBytes, uint32(len(receiverBytes)))
borsh = append(borsh, lenBytes...)
borsh = append(borsh, receiverBytes...)
// Block hash
borsh = append(borsh, tx.BlockHash[:]...)
// Actions count
actionsLen := make([]byte, 4)
binary.LittleEndian.PutUint32(actionsLen, uint32(len(tx.Actions)))
borsh = append(borsh, actionsLen...)
// Serialize each action
for _, action := range tx.Actions {
borsh = append(borsh, n.serializeActionToBorsh(action)...)
}
return borsh
}
// serializeActionToBorsh serializes a NEAR action
func (n *NEARAdapter) serializeActionToBorsh(action NEARAction) []byte {
var borsh []byte
// Action type
borsh = append(borsh, byte(action.Type()))
// Action-specific data (simplified)
switch a := action.(type) {
case *TransferAction:
borsh = append(borsh, a.Deposit.Amount[:]...)
case *FunctionCallAction:
// Method name
methodBytes := []byte(a.MethodName)
lenBytes := make([]byte, 4)
binary.LittleEndian.PutUint32(lenBytes, uint32(len(methodBytes)))
borsh = append(borsh, lenBytes...)
borsh = append(borsh, methodBytes...)
// Args
lenBytes = make([]byte, 4)
binary.LittleEndian.PutUint32(lenBytes, uint32(len(a.Args)))
borsh = append(borsh, lenBytes...)
borsh = append(borsh, a.Args...)
// Gas
gasBytes := make([]byte, 8)
binary.LittleEndian.PutUint64(gasBytes, a.Gas)
borsh = append(borsh, gasBytes...)
// Deposit
borsh = append(borsh, a.Deposit.Amount[:]...)
}
return borsh
}
// GenerateNEARAddress generates a NEAR account ID from public key
func (n *NEARAdapter) GenerateNEARAddress(publicKey [32]byte, accountID string) string {
// NEAR uses human-readable account IDs, not derived from public keys
// Format: accountname.near or accountname.testnet
if accountID == "" {
// Generate a default account ID from public key (for testing)
h := sha3.New256()
h.Write(publicKey[:])
hash := h.Sum(nil)
accountID = fmt.Sprintf("%x", hash[:8])
}
// Add network suffix
switch n.networkID {
case "mainnet":
return accountID + ".near"
case "testnet":
return accountID + ".testnet"
default:
return accountID
}
}
// EstimateGas estimates gas for NEAR transaction
func (n *NEARAdapter) EstimateGas(tx *NEARTransaction) uint64 {
// Base cost in TGas (10^12 gas units)
baseCost := uint64(1_000_000_000_000)
// Add cost per action
actionCost := uint64(0)
for _, action := range tx.Actions {
switch action.Type() {
case Transfer:
actionCost += 115_000_000_000
case FunctionCall:
actionCost += 2_500_000_000_000
case DeployContract:
actionCost += 10_000_000_000_000
default:
actionCost += 1_000_000_000_000
}
}
return baseCost + actionCost
}
// GetNEARConfig returns default NEAR configuration
func GetDefaultNEARConfig(networkID string) map[string]interface{} {
return map[string]interface{}{
"network_id": networkID,
"signature_type": SignatureEdDSA,
"curve": "Ed25519",
"hash_algorithm": "SHA3-256",
"serialization": "Borsh",
"rpc_url": fmt.Sprintf("https://rpc.%s.near.org", networkID),
}
}