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

680 lines
16 KiB
Go

// Package adapters - Cardano adapter for Ed25519 threshold signatures
package adapters
import (
"crypto/sha256"
"encoding/binary"
"errors"
"fmt"
"github.com/luxfi/threshold/pkg/math/curve"
"golang.org/x/crypto/blake2b"
)
// CardanoAdapter implements SignerAdapter for Cardano blockchain
// Cardano natively uses Ed25519 but also supports ECDSA/Schnorr for interoperability
type CardanoAdapter struct {
group curve.Curve
sigType SignatureType
networkID byte // 0x00 for testnet, 0x01 for mainnet
era CardanoEra
}
// CardanoEra represents different Cardano protocol eras
type CardanoEra int
const (
EraShelley CardanoEra = iota
EraAllegra
EraMary
EraAlonzo
EraBabbage // Current era with Plutus V2
EraConway // Upcoming with governance
)
// NewCardanoAdapter creates a new Cardano adapter. Returns an error if
// sigType is not one of EdDSA / ECDSA / Schnorr (Cardano's three valid
// signature schemes for Babbage+ eras).
func NewCardanoAdapter(sigType SignatureType, networkID byte, era CardanoEra) (*CardanoAdapter, error) {
var group curve.Curve
switch sigType {
case SignatureEdDSA:
group = curve.Ed25519{}
case SignatureECDSA:
group = curve.Secp256k1{}
case SignatureSchnorr:
group = curve.Secp256k1{}
default:
return nil, fmt.Errorf("cardano: unsupported signature type %v (want SignatureEdDSA, SignatureECDSA, or SignatureSchnorr)", sigType)
}
return &CardanoAdapter{
group: group,
sigType: sigType,
networkID: networkID,
era: era,
}, nil
}
// Digest computes Cardano transaction digest
func (c *CardanoAdapter) Digest(tx interface{}) ([]byte, error) {
switch v := tx.(type) {
case *CardanoTransaction:
return c.digestTransaction(v)
case *CardanoMetadata:
return c.digestMetadata(v)
case []byte:
// Raw CBOR bytes
return c.hashCBOR(v), nil
default:
return nil, fmt.Errorf("unsupported Cardano transaction type: %T", tx)
}
}
// digestTransaction computes digest for Cardano transaction
func (c *CardanoAdapter) digestTransaction(tx *CardanoTransaction) ([]byte, error) {
// Cardano uses CBOR encoding with Blake2b-256 hash
cbor := c.serializeToCBOR(tx)
return c.hashCBOR(cbor), nil
}
// digestMetadata computes digest for Cardano metadata
func (c *CardanoAdapter) digestMetadata(metadata *CardanoMetadata) ([]byte, error) {
cbor := c.serializeMetadataToCBOR(metadata)
return c.hashCBOR(cbor), nil
}
// hashCBOR computes Blake2b-256 hash of CBOR data
func (c *CardanoAdapter) hashCBOR(cbor []byte) []byte {
h, _ := blake2b.New256(nil)
h.Write(cbor)
return h.Sum(nil)
}
// SignEC creates partial signature for Cardano
func (c *CardanoAdapter) SignEC(digest []byte, share Share) (PartialSig, error) {
switch c.sigType {
case SignatureEdDSA:
// Native Cardano signature (Ed25519)
// For testing, provide a placeholder R value
z, err := coerceScalar(c.group, share.Value)
if err != nil {
return nil, fmt.Errorf("cardano: %w", err)
}
return &EdDSAPartialSig{
PartyID: share.ID,
R: c.group.NewBasePoint(), // Placeholder for testing
Z: z,
}, nil
case SignatureECDSA:
// For cross-chain compatibility
// For testing, provide a placeholder R value
return &ECDSAPartialSig{
PartyID: share.ID,
R: c.group.NewScalar(), // Placeholder for testing
S: share.Value,
}, nil
case SignatureSchnorr:
// For interoperability
// For testing, provide a placeholder R value
return &SchnorrPartialSig{
PartyID: share.ID,
R: c.group.NewBasePoint(), // Placeholder for testing
S: share.Value,
}, nil
default:
return nil, fmt.Errorf("unsupported signature type: %v", c.sigType)
}
}
// AggregateEC combines partial signatures
func (c *CardanoAdapter) AggregateEC(parts []PartialSig) (FullSig, error) {
switch c.sigType {
case SignatureEdDSA:
return c.aggregateEd25519(parts)
case SignatureECDSA:
return c.aggregateECDSA(parts)
case SignatureSchnorr:
return c.aggregateSchnorr(parts)
default:
return nil, fmt.Errorf("unsupported signature type: %v", c.sigType)
}
}
// aggregateEd25519 combines Ed25519 partial signatures
func (c *CardanoAdapter) aggregateEd25519(parts []PartialSig) (FullSig, error) {
if len(parts) == 0 {
return nil, errors.New("no partial signatures")
}
var r curve.Point
z := c.group.NewScalar()
expectedCurve := c.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("cardano: ed25519 partial[%d] has nil scalar Z", i)
}
if got := eddsaPart.Z.Curve().Name(); got != expectedCurve {
return nil, fmt.Errorf("cardano: 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
}
// aggregateECDSA combines ECDSA partial signatures
func (c *CardanoAdapter) aggregateECDSA(parts []PartialSig) (FullSig, error) {
var r, s curve.Scalar
for _, part := range parts {
ecdsaPart, ok := part.(*ECDSAPartialSig)
if !ok {
return nil, errors.New("invalid ECDSA partial signature")
}
if r == nil && ecdsaPart.R != nil {
r = ecdsaPart.R
}
if s == nil {
s = c.group.NewScalar()
}
s = s.Add(ecdsaPart.S)
}
return &ECDSAFullSig{
R: r,
S: s,
}, nil
}
// aggregateSchnorr combines Schnorr partial signatures
func (c *CardanoAdapter) aggregateSchnorr(parts []PartialSig) (FullSig, error) {
var r curve.Point
s := c.group.NewScalar()
for _, part := range parts {
schnorrPart, ok := part.(*SchnorrPartialSig)
if !ok {
return nil, errors.New("invalid Schnorr partial signature")
}
if r == nil && schnorrPart.R != nil {
r = schnorrPart.R
}
s = s.Add(schnorrPart.S)
}
return &SchnorrFullSig{
R: r,
S: s,
}, nil
}
// Encode formats signature for Cardano
func (c *CardanoAdapter) Encode(full FullSig) ([]byte, error) {
switch c.sigType {
case SignatureEdDSA:
return c.encodeEd25519(full)
case SignatureECDSA:
return c.encodeECDSA(full)
case SignatureSchnorr:
return c.encodeSchnorr(full)
default:
return nil, fmt.Errorf("unsupported signature type: %v", c.sigType)
}
}
// encodeEd25519 encodes Ed25519 signature for Cardano
func (c *CardanoAdapter) encodeEd25519(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)
rBytes, _ := eddsaSig.R.MarshalBinary()
// Skip format byte if present (first byte is 0x02, 0x03, or 0x04)
if len(rBytes) == 33 && (rBytes[0] == 0x02 || rBytes[0] == 0x03 || rBytes[0] == 0x04) {
rBytes = rBytes[1:] // Skip format byte
}
if len(rBytes) > 32 {
return nil, fmt.Errorf("invalid R length: %d", len(rBytes))
}
copy(sig[:32], rBytes)
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
}
// encodeECDSA encodes ECDSA signature for Cardano Plutus
func (c *CardanoAdapter) encodeECDSA(full FullSig) ([]byte, error) {
ecdsaSig, ok := full.(*ECDSAFullSig)
if !ok {
return nil, errors.New("invalid ECDSA signature")
}
// ECDSA: r (32 bytes) || s (32 bytes)
sig := make([]byte, 64)
rBytes, _ := ecdsaSig.R.MarshalBinary()
copy(sig[32-len(rBytes):32], rBytes)
sBytes, _ := ecdsaSig.S.MarshalBinary()
copy(sig[64-len(sBytes):], sBytes)
return sig, nil
}
// encodeSchnorr encodes Schnorr signature for Cardano
func (c *CardanoAdapter) encodeSchnorr(full FullSig) ([]byte, error) {
schnorrSig, ok := full.(*SchnorrFullSig)
if !ok {
return nil, errors.New("invalid Schnorr signature")
}
// Schnorr: R (32 bytes) || s (32 bytes)
sig := make([]byte, 64)
rBytes, _ := schnorrSig.R.MarshalBinary()
copy(sig[:32], rBytes)
sBytes, _ := schnorrSig.S.MarshalBinary()
copy(sig[32+(32-len(sBytes)):], sBytes)
return sig, nil
}
// ValidateConfig validates Cardano-specific configuration
func (c *CardanoAdapter) ValidateConfig(config *UnifiedConfig) error {
if config.SignatureScheme != c.sigType {
return fmt.Errorf("config signature type %v doesn't match adapter type %v",
config.SignatureScheme, c.sigType)
}
return nil
}
// Cardano-specific structures
type CardanoTransaction struct {
Body *TransactionBody
Witnesses *TransactionWitnessSet
IsValid bool
AuxData *CardanoMetadata // Optional metadata
}
type TransactionBody struct {
Inputs []TransactionInput
Outputs []TransactionOutput
Fee uint64
TTL uint32 // Time to live (slot)
Certificates []Certificate
Withdrawals map[string]uint64 // Stake address -> amount
Update *ProtocolUpdate
AuxDataHash [32]byte
ValidityInterval *ValidityInterval
Mint map[PolicyID]map[AssetName]int64
ScriptDataHash [32]byte
Collateral []TransactionInput
RequiredSigners [][28]byte // Key hashes
NetworkID byte
CollateralReturn *TransactionOutput
TotalCollateral uint64
ReferenceInputs []TransactionInput
}
type TransactionInput struct {
TxID [32]byte
Index uint32
}
type TransactionOutput struct {
Address CardanoAddress
Value Value
DatumHash *[32]byte // Optional for smart contracts
Data []byte // Inline datum (Babbage era)
Script []byte // Reference script (Babbage era)
}
type CardanoAddress struct {
Type AddressType
Network byte
Payment [28]byte // Payment credential hash
Stake [28]byte // Stake credential hash (optional)
}
type AddressType byte
const (
BaseAddress AddressType = iota
ScriptAddress
EnterpriseAddress
PointerAddress
RewardAddress
)
type Value struct {
Coin uint64
MultiAsset map[PolicyID]map[AssetName]uint64
}
type PolicyID [28]byte
type AssetName string
type Certificate interface {
Type() CertificateType
}
type CertificateType byte
const (
StakeRegistration CertificateType = iota
StakeDeregistration
StakeDelegation
PoolRegistration
PoolRetirement
GenesisKeyDelegation
MoveInstantaneousRewards
)
type ValidityInterval struct {
InvalidBefore uint32 // Slot
InvalidAfter uint32 // Slot
}
type ProtocolUpdate struct {
Epoch uint32
ProtocolParams *ProtocolParameters
}
type ProtocolParameters struct {
MinFeeA uint32
MinFeeB uint32
MaxBlockBodySize uint32
MaxTxSize uint32
MaxBlockHeaderSize uint32
KeyDeposit uint64
PoolDeposit uint64
MinPoolCost uint64
PriceMemory Rational
PriceSteps Rational
MaxTxExecutionUnits ExecutionUnits
MaxBlockExecutionUnits ExecutionUnits
MaxValueSize uint32
CollateralPercentage uint32
MaxCollateralInputs uint32
}
type Rational struct {
Numerator uint64
Denominator uint64
}
type ExecutionUnits struct {
Memory uint64
Steps uint64
}
type TransactionWitnessSet struct {
VKeyWitnesses []VKeyWitness
Scripts []Script
PlutusData [][]byte
Redeemers []Redeemer
NativeScripts []NativeScript
}
type VKeyWitness struct {
VKey [32]byte // Public key
Signature [64]byte // Ed25519 signature
}
type Script interface {
Hash() [28]byte
}
type Redeemer struct {
Tag RedeemerTag
Index uint32
Data []byte
ExUnits ExecutionUnits
}
type RedeemerTag byte
const (
SpendRedeemer RedeemerTag = iota
MintRedeemer
CertRedeemer
RewardRedeemer
)
type NativeScript interface {
Type() NativeScriptType
}
type NativeScriptType byte
const (
ScriptPubkey NativeScriptType = iota
ScriptAll
ScriptAny
ScriptNofK
InvalidBefore
InvalidAfter
)
type CardanoMetadata struct {
Labels map[uint64]interface{}
}
// Note: SchnorrPartialSig and SchnorrFullSig are defined in bitcoin.go
// serializeToCBOR serializes a Cardano transaction to CBOR format
func (c *CardanoAdapter) serializeToCBOR(tx *CardanoTransaction) []byte {
// Simplified CBOR serialization
// Actual implementation would use proper CBOR encoding
var cbor []byte
// Transaction body
cbor = append(cbor, c.serializeTransactionBody(tx.Body)...)
// Witnesses
if tx.Witnesses != nil {
cbor = append(cbor, c.serializeWitnessSet(tx.Witnesses)...)
}
// IsValid flag
if tx.IsValid {
cbor = append(cbor, 1)
} else {
cbor = append(cbor, 0)
}
// Auxiliary data
if tx.AuxData != nil {
cbor = append(cbor, c.serializeMetadataToCBOR(tx.AuxData)...)
}
return cbor
}
// serializeTransactionBody serializes transaction body to CBOR
func (c *CardanoAdapter) serializeTransactionBody(body *TransactionBody) []byte {
var cbor []byte
// Inputs
for _, input := range body.Inputs {
cbor = append(cbor, input.TxID[:]...)
indexBytes := make([]byte, 4)
binary.BigEndian.PutUint32(indexBytes, input.Index)
cbor = append(cbor, indexBytes...)
}
// Outputs
for _, output := range body.Outputs {
cbor = append(cbor, c.serializeOutput(&output)...)
}
// Fee
feeBytes := make([]byte, 8)
binary.BigEndian.PutUint64(feeBytes, body.Fee)
cbor = append(cbor, feeBytes...)
// TTL
if body.TTL > 0 {
ttlBytes := make([]byte, 4)
binary.BigEndian.PutUint32(ttlBytes, body.TTL)
cbor = append(cbor, ttlBytes...)
}
return cbor
}
// serializeOutput serializes transaction output
func (c *CardanoAdapter) serializeOutput(output *TransactionOutput) []byte {
var data []byte
// Address
data = append(data, c.serializeAddress(&output.Address)...)
// Value
valueBytes := make([]byte, 8)
binary.BigEndian.PutUint64(valueBytes, output.Value.Coin)
data = append(data, valueBytes...)
// Optional datum hash
if output.DatumHash != nil {
data = append(data, output.DatumHash[:]...)
}
return data
}
// serializeAddress serializes Cardano address
func (c *CardanoAdapter) serializeAddress(addr *CardanoAddress) []byte {
var data []byte
// Address type and network
header := byte(addr.Type)<<4 | (addr.Network & 0x0F)
data = append(data, header)
// Payment credential
data = append(data, addr.Payment[:]...)
// Stake credential (if present)
if addr.Type == BaseAddress {
data = append(data, addr.Stake[:]...)
}
return data
}
// serializeWitnessSet serializes witness set
func (c *CardanoAdapter) serializeWitnessSet(witnesses *TransactionWitnessSet) []byte {
var data []byte
// VKey witnesses
for _, vkey := range witnesses.VKeyWitnesses {
data = append(data, vkey.VKey[:]...)
data = append(data, vkey.Signature[:]...)
}
return data
}
// serializeMetadataToCBOR serializes metadata to CBOR
func (c *CardanoAdapter) serializeMetadataToCBOR(metadata *CardanoMetadata) []byte {
// Simplified - actual would use proper CBOR encoding
h := sha256.New()
for label, value := range metadata.Labels {
labelBytes := make([]byte, 8)
binary.BigEndian.PutUint64(labelBytes, label)
h.Write(labelBytes)
fmt.Fprintf(h, "%v", value)
}
return h.Sum(nil)
}
// GenerateCardanoAddress generates a Cardano address from public key
func (c *CardanoAdapter) GenerateCardanoAddress(paymentPubKey, stakePubKey [32]byte) CardanoAddress {
// Hash payment and stake keys using Blake2b-224
paymentHasher, _ := blake2b.New(28, nil) // 224 bits = 28 bytes
paymentHasher.Write(paymentPubKey[:])
paymentHash := paymentHasher.Sum(nil)
stakeHasher, _ := blake2b.New(28, nil)
stakeHasher.Write(stakePubKey[:])
stakeHash := stakeHasher.Sum(nil)
addr := CardanoAddress{
Type: BaseAddress,
Network: c.networkID,
}
copy(addr.Payment[:], paymentHash)
copy(addr.Stake[:], stakeHash)
return addr
}
// EstimateFee estimates transaction fee in Lovelace
func (c *CardanoAdapter) EstimateFee(tx *CardanoTransaction) uint64 {
// Cardano fee calculation: a * size + b
// Default mainnet: a = 44, b = 155381
txSize := len(c.serializeToCBOR(tx))
minFeeA := uint64(44)
minFeeB := uint64(155381)
fee := minFeeA*uint64(txSize) + minFeeB
// Add script execution costs if present
if tx.Witnesses != nil && len(tx.Witnesses.Redeemers) > 0 {
for _, redeemer := range tx.Witnesses.Redeemers {
// Price per memory unit and step
fee += redeemer.ExUnits.Memory * 577 / 10000
fee += redeemer.ExUnits.Steps * 721 / 10000000
}
}
return fee
}
// GetCardanoConfig returns default Cardano configuration
func GetDefaultCardanoConfig(networkID byte, era CardanoEra) map[string]interface{} {
return map[string]interface{}{
"network_id": networkID,
"era": era,
"signature_type": SignatureEdDSA, // Native
"curve": "Ed25519",
"hash_algorithm": "Blake2b-256",
"encoding": "CBOR",
"min_fee_a": 44,
"min_fee_b": 155381,
}
}