mirror of
https://github.com/luxfi/threshold.git
synced 2026-10-30 15:15:53 +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.
680 lines
16 KiB
Go
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,
|
|
}
|
|
}
|