mirror of
https://github.com/luxfi/threshold.git
synced 2026-07-27 04:01:59 +00:00
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.
421 lines
10 KiB
Go
421 lines
10 KiB
Go
// Package adapters - TON blockchain adapter for Ed25519 threshold signatures
|
|
package adapters
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/luxfi/threshold/pkg/math/curve"
|
|
)
|
|
|
|
// TONAdapter implements SignerAdapter for TON blockchain
|
|
// TON uses Ed25519 for signatures and supports Curve25519 conversion
|
|
type TONAdapter struct {
|
|
group curve.Curve
|
|
workchain int32 // TON workchain ID (-1 for masterchain, 0 for basechain)
|
|
}
|
|
|
|
// NewTONAdapter creates a new TON adapter
|
|
func NewTONAdapter(workchain int32) *TONAdapter {
|
|
return &TONAdapter{
|
|
group: curve.Ed25519{},
|
|
workchain: workchain,
|
|
}
|
|
}
|
|
|
|
// Digest computes TON message digest using BOC (Bag of Cells) hash
|
|
func (t *TONAdapter) Digest(tx interface{}) ([]byte, error) {
|
|
switch v := tx.(type) {
|
|
case *TONMessage:
|
|
return t.digestMessage(v)
|
|
case *TONTransaction:
|
|
return t.digestTransaction(v)
|
|
case []byte:
|
|
// Raw BOC bytes
|
|
return t.hashBOC(v), nil
|
|
default:
|
|
return nil, fmt.Errorf("unsupported TON transaction type: %T", tx)
|
|
}
|
|
}
|
|
|
|
// digestMessage computes digest for TON message
|
|
func (t *TONAdapter) digestMessage(msg *TONMessage) ([]byte, error) {
|
|
// TON uses BOC representation hash
|
|
boc := t.serializeToBOC(msg)
|
|
return t.hashBOC(boc), nil
|
|
}
|
|
|
|
// digestTransaction computes digest for TON transaction
|
|
func (t *TONAdapter) digestTransaction(tx *TONTransaction) ([]byte, error) {
|
|
// Serialize transaction to BOC format
|
|
boc := t.serializeTransactionToBOC(tx)
|
|
return t.hashBOC(boc), nil
|
|
}
|
|
|
|
// hashBOC computes SHA-256 hash of Bag of Cells
|
|
func (t *TONAdapter) hashBOC(boc []byte) []byte {
|
|
h := sha256.New()
|
|
h.Write(boc)
|
|
return h.Sum(nil)
|
|
}
|
|
|
|
// SignEC creates Ed25519 partial signature for TON
|
|
func (t *TONAdapter) 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(t.group, share.Value)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("ton: %w", err)
|
|
}
|
|
return &EdDSAPartialSig{
|
|
PartyID: share.ID,
|
|
R: t.group.NewBasePoint(), // Placeholder for testing
|
|
Z: z,
|
|
}, nil
|
|
}
|
|
|
|
// AggregateEC combines Ed25519 partial signatures
|
|
func (t *TONAdapter) AggregateEC(parts []PartialSig) (FullSig, error) {
|
|
if len(parts) == 0 {
|
|
return nil, errors.New("no partial signatures")
|
|
}
|
|
|
|
var r curve.Point
|
|
z := t.group.NewScalar()
|
|
expectedCurve := t.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("ton: ed25519 partial[%d] has nil scalar Z", i)
|
|
}
|
|
if got := eddsaPart.Z.Curve().Name(); got != expectedCurve {
|
|
return nil, fmt.Errorf("ton: 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 TON
|
|
func (t *TONAdapter) 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 (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)
|
|
|
|
// 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 TON-specific configuration
|
|
func (t *TONAdapter) ValidateConfig(config *UnifiedConfig) error {
|
|
if config.SignatureScheme != SignatureEdDSA {
|
|
return errors.New("TON requires Ed25519 signatures")
|
|
}
|
|
|
|
if _, ok := config.Group.(curve.Ed25519); !ok {
|
|
return errors.New("TON requires Ed25519 curve")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// TON-specific structures
|
|
|
|
type TONMessage struct {
|
|
Info TONMessageInfo
|
|
Init *TONStateInit // Optional
|
|
Body []byte
|
|
}
|
|
|
|
type TONMessageInfo struct {
|
|
IHRDisabled bool
|
|
Bounce bool
|
|
Bounced bool
|
|
Source TONAddress
|
|
Destination TONAddress
|
|
Value TONCurrencyCollection
|
|
IHRFee uint64
|
|
FwdFee uint64
|
|
CreatedLt uint64
|
|
CreatedAt uint32
|
|
}
|
|
|
|
type TONAddress struct {
|
|
Workchain int32
|
|
Hash [32]byte
|
|
}
|
|
|
|
type TONStateInit struct {
|
|
Code []byte
|
|
Data []byte
|
|
Library []byte
|
|
}
|
|
|
|
type TONCurrencyCollection struct {
|
|
Grams uint64
|
|
ExtraCurrencies map[uint32]uint64
|
|
}
|
|
|
|
type TONTransaction struct {
|
|
Account TONAddress
|
|
Lt uint64
|
|
PrevTransHash [32]byte
|
|
PrevTransLt uint64
|
|
Now uint32
|
|
OutMsgCount uint16
|
|
OrigStatus AccountStatus
|
|
EndStatus AccountStatus
|
|
InMsg *TONMessage
|
|
OutMsgs []*TONMessage
|
|
}
|
|
|
|
type AccountStatus byte
|
|
|
|
const (
|
|
AccountUninit AccountStatus = iota
|
|
AccountActive
|
|
AccountFrozen
|
|
)
|
|
|
|
// serializeToBOC serializes a TON message to Bag of Cells format
|
|
func (t *TONAdapter) serializeToBOC(msg *TONMessage) []byte {
|
|
// Simplified BOC serialization
|
|
// Actual implementation would follow TON's BOC specification
|
|
var boc []byte
|
|
|
|
// Serialize message info
|
|
boc = append(boc, t.serializeMessageInfo(&msg.Info)...)
|
|
|
|
// Serialize optional init
|
|
if msg.Init != nil {
|
|
boc = append(boc, 1) // Has init
|
|
boc = append(boc, t.serializeStateInit(msg.Init)...)
|
|
} else {
|
|
boc = append(boc, 0) // No init
|
|
}
|
|
|
|
// Serialize body
|
|
boc = append(boc, msg.Body...)
|
|
|
|
return boc
|
|
}
|
|
|
|
// serializeTransactionToBOC serializes a TON transaction to BOC format
|
|
func (t *TONAdapter) serializeTransactionToBOC(tx *TONTransaction) []byte {
|
|
var boc []byte
|
|
|
|
// Account address
|
|
boc = append(boc, t.serializeAddress(&tx.Account)...)
|
|
|
|
// Logical time
|
|
ltBytes := make([]byte, 8)
|
|
binary.BigEndian.PutUint64(ltBytes, tx.Lt)
|
|
boc = append(boc, ltBytes...)
|
|
|
|
// Previous transaction hash
|
|
boc = append(boc, tx.PrevTransHash[:]...)
|
|
|
|
// Previous transaction lt
|
|
prevLtBytes := make([]byte, 8)
|
|
binary.BigEndian.PutUint64(prevLtBytes, tx.PrevTransLt)
|
|
boc = append(boc, prevLtBytes...)
|
|
|
|
// Timestamp
|
|
nowBytes := make([]byte, 4)
|
|
binary.BigEndian.PutUint32(nowBytes, tx.Now)
|
|
boc = append(boc, nowBytes...)
|
|
|
|
// Out messages count
|
|
outCountBytes := make([]byte, 2)
|
|
binary.BigEndian.PutUint16(outCountBytes, tx.OutMsgCount)
|
|
boc = append(boc, outCountBytes...)
|
|
|
|
// Account statuses
|
|
boc = append(boc, byte(tx.OrigStatus))
|
|
boc = append(boc, byte(tx.EndStatus))
|
|
|
|
return boc
|
|
}
|
|
|
|
// serializeMessageInfo serializes TON message info
|
|
func (t *TONAdapter) serializeMessageInfo(info *TONMessageInfo) []byte {
|
|
var data []byte
|
|
|
|
// Flags
|
|
flags := byte(0)
|
|
if info.IHRDisabled {
|
|
flags |= 0x01
|
|
}
|
|
if info.Bounce {
|
|
flags |= 0x02
|
|
}
|
|
if info.Bounced {
|
|
flags |= 0x04
|
|
}
|
|
data = append(data, flags)
|
|
|
|
// Addresses
|
|
data = append(data, t.serializeAddress(&info.Source)...)
|
|
data = append(data, t.serializeAddress(&info.Destination)...)
|
|
|
|
// Value
|
|
valueBytes := make([]byte, 8)
|
|
binary.BigEndian.PutUint64(valueBytes, info.Value.Grams)
|
|
data = append(data, valueBytes...)
|
|
|
|
// Fees
|
|
ihrFeeBytes := make([]byte, 8)
|
|
binary.BigEndian.PutUint64(ihrFeeBytes, info.IHRFee)
|
|
data = append(data, ihrFeeBytes...)
|
|
|
|
fwdFeeBytes := make([]byte, 8)
|
|
binary.BigEndian.PutUint64(fwdFeeBytes, info.FwdFee)
|
|
data = append(data, fwdFeeBytes...)
|
|
|
|
// Logical time and creation time
|
|
ltBytes := make([]byte, 8)
|
|
binary.BigEndian.PutUint64(ltBytes, info.CreatedLt)
|
|
data = append(data, ltBytes...)
|
|
|
|
atBytes := make([]byte, 4)
|
|
binary.BigEndian.PutUint32(atBytes, info.CreatedAt)
|
|
data = append(data, atBytes...)
|
|
|
|
return data
|
|
}
|
|
|
|
// serializeAddress serializes a TON address
|
|
func (t *TONAdapter) serializeAddress(addr *TONAddress) []byte {
|
|
var data []byte
|
|
|
|
// Workchain (variable length integer)
|
|
wcBytes := make([]byte, 4)
|
|
binary.BigEndian.PutUint32(wcBytes, uint32(addr.Workchain))
|
|
data = append(data, wcBytes...)
|
|
|
|
// Address hash
|
|
data = append(data, addr.Hash[:]...)
|
|
|
|
return data
|
|
}
|
|
|
|
// serializeStateInit serializes TON state init
|
|
func (t *TONAdapter) serializeStateInit(init *TONStateInit) []byte {
|
|
var data []byte
|
|
|
|
// Has code
|
|
if len(init.Code) > 0 {
|
|
data = append(data, 1)
|
|
data = append(data, init.Code...)
|
|
} else {
|
|
data = append(data, 0)
|
|
}
|
|
|
|
// Has data
|
|
if len(init.Data) > 0 {
|
|
data = append(data, 1)
|
|
data = append(data, init.Data...)
|
|
} else {
|
|
data = append(data, 0)
|
|
}
|
|
|
|
// Has library
|
|
if len(init.Library) > 0 {
|
|
data = append(data, 1)
|
|
data = append(data, init.Library...)
|
|
} else {
|
|
data = append(data, 0)
|
|
}
|
|
|
|
return data
|
|
}
|
|
|
|
// GenerateTONAddress generates a TON address from public key
|
|
func (t *TONAdapter) GenerateTONAddress(publicKey [32]byte) TONAddress {
|
|
// TON address = workchain:hash(stateInit)
|
|
// Simplified version - actual would compute from StateInit
|
|
h := sha256.Sum256(publicKey[:])
|
|
|
|
return TONAddress{
|
|
Workchain: t.workchain,
|
|
Hash: h,
|
|
}
|
|
}
|
|
|
|
// CreateWalletStateInit creates initial state for TON wallet
|
|
func (t *TONAdapter) CreateWalletStateInit(publicKey [32]byte, walletID uint32) *TONStateInit {
|
|
// This would create the actual wallet contract StateInit
|
|
// Simplified placeholder
|
|
return &TONStateInit{
|
|
Code: []byte("wallet_v4_code"), // Actual wallet code
|
|
Data: append(publicKey[:], make([]byte, 4)...), // pubkey + wallet_id
|
|
}
|
|
}
|
|
|
|
// EstimateGas estimates gas for TON transaction
|
|
func (t *TONAdapter) EstimateGas(msg *TONMessage) uint64 {
|
|
// Base computation cost
|
|
baseCost := uint64(10000)
|
|
|
|
// Message size cost
|
|
msgSize := len(t.serializeToBOC(msg))
|
|
sizeCost := uint64(msgSize * 10)
|
|
|
|
// Storage cost if deploying contract
|
|
storageCost := uint64(0)
|
|
if msg.Init != nil {
|
|
storageCost = 50000
|
|
}
|
|
|
|
return baseCost + sizeCost + storageCost
|
|
}
|
|
|
|
// GetTONConfig returns default TON configuration
|
|
func GetDefaultTONConfig(workchain int32) map[string]interface{} {
|
|
return map[string]interface{}{
|
|
"workchain": workchain,
|
|
"signature_type": SignatureEdDSA,
|
|
"curve": "Ed25519",
|
|
"hash_algorithm": "SHA256",
|
|
"boc_format": "standard",
|
|
"wallet_version": "v4r2",
|
|
}
|
|
}
|