Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
75fbec2
SOL: Mark concurrent durable nonce account creation as conflicting
cordialsys-agent Mar 24, 2026
dd6faa1
Add Monero (XMR) chain driver with subaddress support
cordialsys-agent Mar 31, 2026
f678679
Fix Monero balance scanning: batch tx requests for restricted nodes
cordialsys-agent Mar 31, 2026
1a7c038
Add Bulletproofs+ range proofs and Monero tx construction
cordialsys-agent Mar 31, 2026
287c04e
Add CLSAG ring signatures, decoy selection, and full transfer flow
cordialsys-agent Mar 31, 2026
d72bd04
Wire decoy ring members into transfer, fix fee estimation
cordialsys-agent Mar 31, 2026
6884c2e
Fix Monero crypto: CGO wrapper for exact hash_to_point, sc_reduce32, …
cordialsys-agent Mar 31, 2026
ef92a8f
Align CLSAG with Monero reference: correct D scaling and ring computa…
cordialsys-agent Apr 1, 2026
bda7f07
Fix CLSAG message: use three-hash structure (prefix || rct_base || bp…
cordialsys-agent Apr 1, 2026
0c80d1b
Fix pseudo-output commitment balance, add CLSAG self-verification tests
cordialsys-agent Apr 1, 2026
4753637
Use Monero's C++ Bulletproofs+ library for correct range proofs
cordialsys-agent Apr 1, 2026
c080fac
Fix tx hash: three-hash structure, CLSAG message from serialized blob
cordialsys-agent Apr 1, 2026
16e7158
Fix CLSAG message: use Tx.Serialize methods directly instead of blob …
cordialsys-agent Apr 2, 2026
a173348
Fix output derivation and amount encryption, add fixed view key
cordialsys-agent Apr 2, 2026
4a76829
Add Monero testnet support
cordialsys-agent Apr 2, 2026
913b18b
Fix CLSAG message: compute from serialized blob for exact byte match
cordialsys-agent Apr 2, 2026
041e122
Fix commitment masks and spent-output filtering
cordialsys-agent Apr 2, 2026
2d44eb3
tx-info uses fixed view key only, no private spend key needed
cordialsys-agent Apr 2, 2026
6f3b806
Recover recipient addresses in tx-info from view key only
cordialsys-agent Apr 2, 2026
cfef382
Populate from in tx-info movements: total spent = sum(outputs) + fee
cordialsys-agent Apr 2, 2026
422d3c0
Use txinfo library constructors for proper field population
cordialsys-agent Apr 2, 2026
03fa58b
Fix fee balance: pass native asset as contract for proper field popul…
cordialsys-agent Apr 2, 2026
bf0ea1d
Add comprehensive unit tests with verified test vectors
cordialsys-agent Apr 2, 2026
fbf9846
Add test vectors for pure Go rewrite validation
cordialsys-agent Apr 2, 2026
48d6c3c
Add CLSAG verification test against real confirmed testnet tx
cordialsys-agent Apr 2, 2026
92690a4
Phase 1: Pure Go ge_fromfe_frombytes_vartime (Elligator map)
cordialsys-agent Apr 2, 2026
338438e
Phase 2-3: Pure Go sc_reduce32, key derivation, key image, H constant
cordialsys-agent Apr 2, 2026
74c59bc
WIP: Pure Go Bulletproofs+ prover (not yet passing verification)
cordialsys-agent Apr 2, 2026
5a74783
Phase 5 partial: Wire pure Go for all crypto except BP+
cordialsys-agent Apr 2, 2026
a5e6dbb
Fix BP+ generators and transcript for pure Go
cordialsys-agent Apr 2, 2026
400c14d
Phase 4 COMPLETE: Pure Go Bulletproofs+ prover passes C++ verification!
cordialsys-agent Apr 2, 2026
fc1e8c3
Phase 5 COMPLETE: Fully pure Go - zero CGO dependencies!
cordialsys-agent Apr 2, 2026
53592c7
Pure Go Monero transfer confirmed on testnet! Zero CGO.
cordialsys-agent Apr 2, 2026
0c341ae
Filter unspendable outputs by verifying commitment mask against chain
cordialsys-agent Apr 2, 2026
5019124
Refactor builder: no private keys, two-phase CLSAG via signer
cordialsys-agent Apr 3, 2026
4ae311c
Fix OOM: replace get_output_distribution with lightweight get_info
cordialsys-agent Apr 3, 2026
414888b
Add monero-lws (Light Wallet Server) support for instant UTXO queries
cordialsys-agent Apr 6, 2026
e81d508
Fix LWS response parsing: per_byte_fee is number not string
cordialsys-agent Apr 6, 2026
6228276
Fix LWS mixin param and two-phase signing
cordialsys-agent Apr 7, 2026
63819d7
Filter spent outputs from LWS, submit via LWS for key image tracking
cordialsys-agent Apr 7, 2026
e6cbd48
Refactor: consolidate duplicates, extract constants, clean up
cordialsys-agent Apr 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions asset.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ const (
MATIC = NativeAsset("MATIC") // Polygon
MON = NativeAsset("MON") // MONAD
NEAR = NativeAsset("NEAR") // Near
XMR = NativeAsset("XMR") // Monero
NOBLE = NativeAsset("NOBLE") // Noble Chain
OAS = NativeAsset("OAS") // Oasys (not Oasis!)
OptETH = NativeAsset("OptETH") // Optimism
Expand Down Expand Up @@ -162,6 +163,7 @@ var NativeAssetList []NativeAsset = []NativeAsset{
MATIC,
MON,
NEAR,
XMR,
OAS,
OptETH,
EmROSE,
Expand Down Expand Up @@ -218,6 +220,7 @@ const (
DriverTon = Driver("ton")
DriverXrp = Driver("xrp")
DriverXlm = Driver("xlm")
DriverMonero = Driver("monero")
DriverZcash = Driver("zcash")
// Crosschain is a client-only driver
DriverCrosschain = Driver("crosschain")
Expand Down Expand Up @@ -247,6 +250,7 @@ var SupportedDrivers = []Driver{
DriverTon,
DriverXrp,
DriverXlm,
DriverMonero,
DriverZcash,
}

Expand Down Expand Up @@ -340,6 +344,8 @@ func (native NativeAsset) Driver() Driver {
return DriverSubstrate
case NEAR:
return DriverNear
case XMR:
return DriverMonero
case TRX:
return DriverTron
case TON:
Expand Down Expand Up @@ -376,7 +382,7 @@ func (driver Driver) SignatureAlgorithms() []SignatureType {
return []SignatureType{K256Sha256}
case DriverEVM, DriverEVMLegacy, DriverCosmosEvmos, DriverTron, DriverHyperliquid, DriverHedera, DriverTempo:
return []SignatureType{K256Keccak}
case DriverAptos, DriverSolana, DriverSui, DriverTon, DriverSubstrate, DriverXlm, DriverCardano, DriverInternetComputerProtocol, DriverNear, DriverEGLD:
case DriverAptos, DriverSolana, DriverSui, DriverTon, DriverSubstrate, DriverXlm, DriverCardano, DriverInternetComputerProtocol, DriverNear, DriverEGLD, DriverMonero:
return []SignatureType{Ed255}
case DriverDusk:
return []SignatureType{Bls12_381G2Blake2}
Expand All @@ -401,7 +407,7 @@ func (driver Driver) PublicKeyFormat() PublicKeyFormat {
case DriverEVM, DriverEVMLegacy, DriverTron, DriverFilecoin, DriverHyperliquid, DriverHedera, DriverTempo:
return Uncompressed
case DriverAptos, DriverSolana, DriverSui, DriverTon, DriverSubstrate, DriverDusk,
DriverKaspa, DriverInternetComputerProtocol, DriverNear, DriverEGLD:
DriverKaspa, DriverInternetComputerProtocol, DriverNear, DriverEGLD, DriverMonero:
return Raw
}
return ""
Expand Down
119 changes: 119 additions & 0 deletions chain/monero/address/address.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package address

import (
"encoding/hex"
"fmt"
"strconv"
"strings"

xc "github.com/cordialsys/crosschain"
xcaddress "github.com/cordialsys/crosschain/address"
moneroCrypto "github.com/cordialsys/crosschain/chain/monero/crypto"
"github.com/cordialsys/crosschain/factory/signer"
)

type AddressBuilder struct {
cfg *xc.ChainBaseConfig
format xc.AddressFormat
}

func NewAddressBuilder(cfg *xc.ChainBaseConfig, options ...xcaddress.AddressOption) (xc.AddressBuilder, error) {
opts, err := xcaddress.NewAddressOptions(options...)
if err != nil {
return nil, err
}
var format xc.AddressFormat
if f, ok := opts.GetFormat(); ok {
format = f
}
return &AddressBuilder{cfg: cfg, format: format}, nil
}

// GetAddressFromPublicKey derives a Monero address from a 64-byte public key
// (publicSpendKey || publicViewKey).
//
// When format is "subaddress:N" or "subaddress:M/N", it generates a subaddress.
// Subaddress generation requires the private view key, which is loaded from
// the XC_PRIVATE_KEY environment variable.
func (ab *AddressBuilder) GetAddressFromPublicKey(publicKeyBytes []byte) (xc.Address, error) {
if len(publicKeyBytes) != 64 {
return "", fmt.Errorf("monero requires 64-byte public key (spend||view), got %d bytes", len(publicKeyBytes))
}

pubSpend := publicKeyBytes[:32]
pubView := publicKeyBytes[32:]

// Determine address prefix based on network
prefix := moneroCrypto.MainnetAddressPrefix
if ab.cfg != nil && (string(ab.cfg.ChainID) == "testnet" || ab.cfg.Network == "testnet") {
prefix = moneroCrypto.TestnetAddressPrefix
}

// Check if subaddress format is requested
formatStr := string(ab.format)
if strings.HasPrefix(formatStr, "subaddress:") {
indexStr := strings.TrimPrefix(formatStr, "subaddress:")
index, err := ParseSubaddressIndex(indexStr)
if err != nil {
return "", fmt.Errorf("invalid subaddress format: %w", err)
}

// For subaddress derivation we need the private view key.
// Derive it from the private spend key in the environment.
privView, err := loadPrivateViewKey()
if err != nil {
return "", fmt.Errorf("subaddress generation requires private key: %w", err)
}

addr, err := moneroCrypto.GenerateSubaddress(privView, pubSpend, index)
if err != nil {
return "", fmt.Errorf("failed to generate subaddress: %w", err)
}
return xc.Address(addr), nil
}

addr := moneroCrypto.GenerateAddressWithPrefix(prefix, pubSpend, pubView)
return xc.Address(addr), nil
}

// loadPrivateViewKey loads the private key from env and derives the view key
func loadPrivateViewKey() ([]byte, error) {
secret := signer.ReadPrivateKeyEnv()
if secret == "" {
return nil, fmt.Errorf("XC_PRIVATE_KEY not set")
}
secretBz, err := hex.DecodeString(secret)
if err != nil {
return nil, fmt.Errorf("failed to decode private key: %w", err)
}
_, privView, _, _, err := moneroCrypto.DeriveKeysFromSpend(secretBz)
if err != nil {
return nil, fmt.Errorf("failed to derive view key: %w", err)
}
return privView, nil
}

// ParseSubaddressIndex parses a format string like "0", "5", "0/3" into a SubaddressIndex.
func ParseSubaddressIndex(format string) (moneroCrypto.SubaddressIndex, error) {
parts := strings.Split(format, "/")
switch len(parts) {
case 1:
minor, err := strconv.ParseUint(parts[0], 10, 32)
if err != nil {
return moneroCrypto.SubaddressIndex{}, fmt.Errorf("invalid subaddress index: %w", err)
}
return moneroCrypto.SubaddressIndex{Major: 0, Minor: uint32(minor)}, nil
case 2:
major, err := strconv.ParseUint(parts[0], 10, 32)
if err != nil {
return moneroCrypto.SubaddressIndex{}, fmt.Errorf("invalid major index: %w", err)
}
minor, err := strconv.ParseUint(parts[1], 10, 32)
if err != nil {
return moneroCrypto.SubaddressIndex{}, fmt.Errorf("invalid minor index: %w", err)
}
return moneroCrypto.SubaddressIndex{Major: uint32(major), Minor: uint32(minor)}, nil
default:
return moneroCrypto.SubaddressIndex{}, fmt.Errorf("invalid format: %s (use N or M/N)", format)
}
}
Loading
Loading