diff --git a/asset.go b/asset.go index 3db21230..633a9c2f 100644 --- a/asset.go +++ b/asset.go @@ -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 @@ -162,6 +163,7 @@ var NativeAssetList []NativeAsset = []NativeAsset{ MATIC, MON, NEAR, + XMR, OAS, OptETH, EmROSE, @@ -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") @@ -247,6 +250,7 @@ var SupportedDrivers = []Driver{ DriverTon, DriverXrp, DriverXlm, + DriverMonero, DriverZcash, } @@ -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: @@ -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} @@ -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 "" diff --git a/chain/monero/address/address.go b/chain/monero/address/address.go new file mode 100644 index 00000000..8ed0dc17 --- /dev/null +++ b/chain/monero/address/address.go @@ -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) + } +} diff --git a/chain/monero/builder/builder.go b/chain/monero/builder/builder.go new file mode 100644 index 00000000..c2614266 --- /dev/null +++ b/chain/monero/builder/builder.go @@ -0,0 +1,504 @@ +package builder + +import ( + "encoding/binary" + "encoding/hex" + "fmt" + "io" + "sort" + + xc "github.com/cordialsys/crosschain" + xcbuilder "github.com/cordialsys/crosschain/builder" + "github.com/cordialsys/crosschain/chain/monero/crypto" + "github.com/cordialsys/crosschain/chain/monero/tx" + "github.com/cordialsys/crosschain/chain/monero/tx_input" + "filippo.io/edwards25519" + "golang.org/x/crypto/sha3" +) + +type TxBuilder struct { + Asset *xc.ChainBaseConfig +} + +func NewTxBuilder(cfg *xc.ChainBaseConfig) (TxBuilder, error) { + return TxBuilder{Asset: cfg}, nil +} + +func (b TxBuilder) Transfer(args xcbuilder.TransferArgs, input xc.TxInput) (xc.Tx, error) { + return b.NewNativeTransfer(args, input) +} + +func (b TxBuilder) NewNativeTransfer(args xcbuilder.TransferArgs, input xc.TxInput) (xc.Tx, error) { + moneroInput, ok := input.(*tx_input.TxInput) + if !ok { + return nil, fmt.Errorf("expected monero TxInput, got %T", input) + } + + // Get sender's public keys from TransferArgs (no private key access) + senderPubKey, ok := args.GetPublicKey() + if !ok || len(senderPubKey) != 64 { + return nil, fmt.Errorf("sender public key required (64 bytes: pubSpend||pubView)") + } + senderPubSpend := senderPubKey[:32] + senderPubView := senderPubKey[32:] + + amountU64 := args.GetAmount().Uint64() + + // Fee estimation + estimatedSize := uint64(2000) + fee := moneroInput.PerByteFee * 200 * estimatedSize / 1024 + if moneroInput.QuantizationMask > 0 { + fee = (fee + moneroInput.QuantizationMask - 1) / moneroInput.QuantizationMask * moneroInput.QuantizationMask + } + if fee < 100000000 { + fee = 100000000 + } + + if len(moneroInput.Outputs) == 0 { + return nil, fmt.Errorf("no spendable outputs available") + } + + // Select outputs (largest first) + sortedOutputs := make([]tx_input.Output, len(moneroInput.Outputs)) + copy(sortedOutputs, moneroInput.Outputs) + sort.Slice(sortedOutputs, func(i, j int) bool { + return sortedOutputs[i].Amount > sortedOutputs[j].Amount + }) + + var selectedOutputs []tx_input.Output + var totalInput uint64 + for _, out := range sortedOutputs { + selectedOutputs = append(selectedOutputs, out) + totalInput += out.Amount + if totalInput >= amountU64+fee { + break + } + } + if totalInput < amountU64+fee { + return nil, fmt.Errorf("insufficient funds: have %d, need %d", totalInput, amountU64+fee) + } + change := totalInput - amountU64 - fee + + // Deterministic RNG from TxInput seed + rngSeed := moneroInput.RngSeed + if len(rngSeed) == 0 { + rngSeed = crypto.Keccak256([]byte("default_rng_seed")) + } + // Include tx-specific data for uniqueness across repeated calls + rngSeed = append(rngSeed, []byte(args.GetTo())...) + rngSeed = append(rngSeed, args.GetAmount().Bytes()...) + for _, out := range selectedOutputs { + rngSeed = append(rngSeed, []byte(out.TxHash)...) + } + rng := newDeterministicRNG(rngSeed) + + // Generate deterministic tx private key + txPrivKey := generateMaskFrom(rng) + txPrivScalar, _ := edwards25519.NewScalar().SetCanonicalBytes(txPrivKey) + txPubKey := edwards25519.NewGeneratorPoint().ScalarBaseMult(txPrivScalar) + + // Build outputs using PUBLIC view keys from addresses (no private keys) + var outputs []tx.TxOutput + var amounts []uint64 + var masks [][]byte + var recipientViews [][]byte + + // Output 0: destination + _, destPubSpend, destPubView, err := crypto.DecodeAddress(string(args.GetTo())) + if err != nil { + return nil, fmt.Errorf("invalid destination address: %w", err) + } + destKey, destViewTag, err := deriveOutputKey(txPrivKey, destPubSpend, destPubView, 0) + if err != nil { + return nil, fmt.Errorf("failed to derive dest key: %w", err) + } + outputs = append(outputs, tx.TxOutput{Amount: 0, PublicKey: destKey, ViewTag: destViewTag}) + amounts = append(amounts, amountU64) + masks = append(masks, deriveOutputMask(txPrivKey, destPubView, 0)) + recipientViews = append(recipientViews, destPubView) + + // Output 1: change (back to sender, using sender's public view key) + if change > 0 { + changeKey, changeViewTag, err := deriveOutputKey(txPrivKey, senderPubSpend, senderPubView, 1) + if err != nil { + return nil, fmt.Errorf("failed to derive change key: %w", err) + } + outputs = append(outputs, tx.TxOutput{Amount: 0, PublicKey: changeKey, ViewTag: changeViewTag}) + amounts = append(amounts, change) + masks = append(masks, deriveOutputMask(txPrivKey, senderPubView, 1)) + recipientViews = append(recipientViews, senderPubView) + } + + // BP+ range proof (deterministic from rng) + var bpFields crypto.BPPlusFields + if len(moneroInput.CachedBpProof) > 0 { + _, bpFields, err = crypto.ParseBPPlusProofGo(moneroInput.CachedBpProof) + if err != nil { + return nil, fmt.Errorf("cached BP+ parse failed: %w", err) + } + } else { + var rawProof []byte + rawProof, err = crypto.BPPlusProvePureGo(amounts, masks, rng) + if err != nil { + return nil, fmt.Errorf("BP+ proof failed: %w", err) + } + moneroInput.CachedBpProof = rawProof + _, bpFields, err = crypto.ParseBPPlusProofGo(rawProof) + if err != nil { + return nil, fmt.Errorf("BP+ parse failed: %w", err) + } + } + + // Compute outPk commitments + commitments := make([]*edwards25519.Point, len(amounts)) + for i := range amounts { + commitments[i], _ = crypto.PedersenCommit(amounts[i], masks[i]) + } + + // Encrypt amounts + var ecdhInfo [][]byte + for i := range amounts { + enc, _ := encryptAmount(amounts[i], txPrivKey, recipientViews[i], i) + ecdhInfo = append(ecdhInfo, enc) + } + + // Extra field: tx public key + extra := []byte{0x01} + extra = append(extra, txPubKey.Bytes()...) + + // Compute pseudo-output commitments + totalOutMask := edwards25519.NewScalar() + for _, mask := range masks { + m, _ := edwards25519.NewScalar().SetCanonicalBytes(mask) + totalOutMask = edwards25519.NewScalar().Add(totalOutMask, m) + } + + pseudoOuts := make([]*edwards25519.Point, len(selectedOutputs)) + pseudoMasks := make([]*edwards25519.Scalar, len(selectedOutputs)) + + if len(selectedOutputs) == 1 { + pseudoMasks[0], _ = edwards25519.NewScalar().SetCanonicalBytes(totalOutMask.Bytes()) + pseudoOuts[0], _ = crypto.PedersenCommit(totalInput, totalOutMask.Bytes()) + } else { + runningMask := edwards25519.NewScalar() + for i := 0; i < len(selectedOutputs)-1; i++ { + pMask := generateMaskFrom(rng) + m, _ := edwards25519.NewScalar().SetCanonicalBytes(pMask) + pseudoMasks[i] = m + runningMask = edwards25519.NewScalar().Add(runningMask, m) + pseudoOuts[i], _ = crypto.PedersenCommit(selectedOutputs[i].Amount, pMask) + } + lastIdx := len(selectedOutputs) - 1 + lastMask := edwards25519.NewScalar().Subtract(totalOutMask, runningMask) + pseudoMasks[lastIdx] = lastMask + pseudoOuts[lastIdx], _ = crypto.PedersenCommit(selectedOutputs[lastIdx].Amount, lastMask.Bytes()) + } + + // Build inputs (key images left empty - computed by signer) + var txInputs []tx.TxInput + var clsagContexts []tx.CLSAGInputContext + + ringSize := 0 + for i, selOut := range selectedOutputs { + ring, ringCommitments, realPos, keyOffsets, err := buildRingFromMembers( + selOut.GlobalIndex, selOut.PublicKey, selOut.Commitment, selOut.RingMembers, + ) + if err != nil { + return nil, fmt.Errorf("failed to build ring for input %d: %w", i, err) + } + + // Use pre-computed commitment mask from TxInput + var inputMask *edwards25519.Scalar + if selOut.CommitmentMask != "" { + maskBytes, _ := hex.DecodeString(selOut.CommitmentMask) + inputMask, _ = edwards25519.NewScalar().SetCanonicalBytes(maskBytes) + } else { + return nil, fmt.Errorf("input %d missing pre-computed commitment mask", i) + } + + // Set real output's commitment from our computed mask + inputCommitment, _ := crypto.PedersenCommit(selOut.Amount, inputMask.Bytes()) + if realPos >= 0 && realPos < len(ringCommitments) { + ringCommitments[realPos] = inputCommitment + } + + // Key image placeholder (32 zero bytes - computed by signer) + keyImage := make([]byte, 32) + + txInputs = append(txInputs, tx.TxInput{ + Amount: 0, + KeyOffsets: keyOffsets, + KeyImage: keyImage, + }) + clsagContexts = append(clsagContexts, tx.CLSAGInputContext{ + Ring: ring, + CNonzero: ringCommitments, + RealPos: realPos, + InputMask: inputMask, + PseudoMask: pseudoMasks[i], + OutputKey: selOut.PublicKey, + TxPubKeyHex: selOut.TxPubKey, + OutputIndex: selOut.Index, + }) + ringSize = len(ring) + } + + // Build the unsigned Tx + moneroTx := &tx.Tx{ + Version: 2, + UnlockTime: 0, + Inputs: txInputs, + Outputs: outputs, + Extra: extra, + RctType: 6, + Fee: fee, + OutCommitments: commitments, + PseudoOuts: pseudoOuts, + EcdhInfo: ecdhInfo, + BpPlusNative: &bpFields, + RingSize: ringSize, + } + + // Compute the CLSAG message from the serialized blob + blobForMsg, err := moneroTx.Serialize() + if err != nil { + return nil, fmt.Errorf("failed to serialize unsigned tx: %w", err) + } + clsagMessage := computeCLSAGMessageFromBlob(blobForMsg, len(txInputs), len(outputs)) + + // Store CLSAG contexts on the Tx for the signer to use + moneroTx.CLSAGContexts = make([]tx.CLSAGInputContext, len(clsagContexts)) + copy(moneroTx.CLSAGContexts, clsagContexts) + for i := range moneroTx.CLSAGContexts { + moneroTx.CLSAGContexts[i].Message = clsagMessage + moneroTx.CLSAGContexts[i].COffset = pseudoOuts[i] + // Create per-input RNG seed for deterministic CLSAG nonces + moneroTx.CLSAGContexts[i].RngSeed = crypto.Keccak256(append(moneroInput.RngSeed, crypto.VarIntEncode(uint64(i))...)) + } + + return moneroTx, nil +} + +func (b TxBuilder) NewTokenTransfer(args xcbuilder.TransferArgs, contract xc.ContractAddress, input xc.TxInput) (xc.Tx, error) { + return nil, fmt.Errorf("monero does not support token transfers") +} + +func (b TxBuilder) SupportsMemo() xc.MemoSupport { + return xc.MemoSupportNone +} + +// deriveOutputKey derives a stealth output key using PUBLIC view key only. +func deriveOutputKey(txPrivKey, pubSpend, pubView []byte, outputIndex int) ([]byte, byte, error) { + D, err := crypto.GenerateKeyDerivation(pubView, txPrivKey) + if err != nil { + return nil, 0, err + } + + scalar, err := crypto.DerivationToScalar(D, uint64(outputIndex)) + if err != nil { + return nil, 0, err + } + + sScalar, _ := edwards25519.NewScalar().SetCanonicalBytes(scalar) + sG := edwards25519.NewGeneratorPoint().ScalarBaseMult(sScalar) + B, _ := edwards25519.NewIdentityPoint().SetBytes(pubSpend) + P := edwards25519.NewIdentityPoint().Add(sG, B) + + viewTagData := append([]byte("view_tag"), D...) + viewTagData = append(viewTagData, crypto.VarIntEncode(uint64(outputIndex))...) + viewTag := crypto.Keccak256(viewTagData)[0] + + return P.Bytes(), viewTag, nil +} + +// deriveOutputMask computes the commitment mask for an output (uses public view key only). +func deriveOutputMask(txPrivKey, recipientPubView []byte, outputIndex int) []byte { + D, _ := crypto.GenerateKeyDerivation(recipientPubView, txPrivKey) + scalar, _ := crypto.DerivationToScalar(D, uint64(outputIndex)) + data := make([]byte, 0, 15+32) + data = append(data, []byte(crypto.CommitmentMaskLabel)...) + data = append(data, scalar...) + return crypto.ScReduce32(crypto.Keccak256(data)) +} + +func encryptAmount(amount uint64, txPrivKey, recipientPubView []byte, outputIndex int) ([]byte, error) { + amountBytes := make([]byte, 8) + binary.LittleEndian.PutUint64(amountBytes, amount) + + D, err := crypto.GenerateKeyDerivation(recipientPubView, txPrivKey) + if err != nil { + return nil, err + } + scalar, _ := crypto.DerivationToScalar(D, uint64(outputIndex)) + amountKey := crypto.Keccak256(append([]byte("amount"), scalar...)) + + encrypted := make([]byte, 8) + for i := 0; i < 8; i++ { + encrypted[i] = amountBytes[i] ^ amountKey[i] + } + return encrypted, nil +} + +func generateMaskFrom(rng io.Reader) []byte { + entropy := make([]byte, 64) + rng.Read(entropy) + return crypto.RandomScalar(entropy) +} + +// buildRingFromMembers constructs a sorted ring from tx_input.RingMember entries. +// See also: client.BuildRing which does the same for client.DecoyOutput types. +func buildRingFromMembers( + realGlobalIndex uint64, realKey string, realCommitment string, + decoys []tx_input.RingMember, +) ([]*edwards25519.Point, []*edwards25519.Point, int, []uint64, error) { + type ringEntry struct { + globalIndex uint64 + key string + commitment string + } + + entries := make([]ringEntry, 0, len(decoys)+1) + entries = append(entries, ringEntry{realGlobalIndex, realKey, realCommitment}) + for _, d := range decoys { + entries = append(entries, ringEntry{d.GlobalIndex, d.PublicKey, d.Commitment}) + } + + sort.Slice(entries, func(i, j int) bool { return entries[i].globalIndex < entries[j].globalIndex }) + + realPos := -1 + ring := make([]*edwards25519.Point, len(entries)) + commitments := make([]*edwards25519.Point, len(entries)) + keyOffsets := make([]uint64, len(entries)) + + var prevIdx uint64 + for i, e := range entries { + if e.globalIndex == realGlobalIndex && e.key == realKey { + realPos = i + } + + keyBytes, err := hex.DecodeString(e.key) + if err != nil || len(keyBytes) != 32 { + ring[i] = edwards25519.NewIdentityPoint() + } else { + p, err := edwards25519.NewIdentityPoint().SetBytes(keyBytes) + if err != nil { + ring[i] = edwards25519.NewIdentityPoint() + } else { + ring[i] = p + } + } + + if e.commitment != "" { + cBytes, _ := hex.DecodeString(e.commitment) + if len(cBytes) == 32 { + p, err := edwards25519.NewIdentityPoint().SetBytes(cBytes) + if err == nil { + commitments[i] = p + } + } + } + if commitments[i] == nil { + commitments[i] = edwards25519.NewIdentityPoint() + } + + keyOffsets[i] = e.globalIndex - prevIdx + prevIdx = e.globalIndex + } + + if realPos < 0 { + return nil, nil, -1, nil, fmt.Errorf("real output not found in ring") + } + + return ring, commitments, realPos, keyOffsets, nil +} + +// computeCLSAGMessageFromBlob parses the tx blob to compute the CLSAG message. +func computeCLSAGMessageFromBlob(blob []byte, numInputs, numOutputs int) []byte { + pos := 0 + readVarint := func() uint64 { + v := uint64(0); s := uint(0) + for blob[pos] & 0x80 != 0 { v |= uint64(blob[pos]&0x7f) << s; s += 7; pos++ } + v |= uint64(blob[pos]) << s; pos++ + return v + } + + readVarint(); readVarint() // version, unlock_time + numIn := readVarint() + for i := uint64(0); i < numIn; i++ { + pos++; readVarint() + count := readVarint() + for j := uint64(0); j < count; j++ { readVarint() } + pos += 32 + } + numOut := readVarint() + for i := uint64(0); i < numOut; i++ { + readVarint(); tag := blob[pos]; pos++; pos += 32 + if tag == 0x03 { pos++ } + } + extraLen := readVarint() + pos += int(extraLen) + prefixEnd := pos + + pos++; readVarint() + pos += int(numOut) * 8 + pos += int(numOut) * 32 + rctBaseEnd := pos + + readVarint() // nbp + kvStart := pos + pos += 6 * 32 + nL := int(readVarint()); pos += nL * 32 + nR := int(readVarint()); pos += nR * 32 + + var kv []byte + kvPos := kvStart + kv = append(kv, blob[kvPos:kvPos+6*32]...) + kvPos += 6 * 32 + for blob[kvPos] & 0x80 != 0 { kvPos++ }; kvPos++ + kv = append(kv, blob[kvPos:kvPos+nL*32]...) + kvPos += nL * 32 + for blob[kvPos] & 0x80 != 0 { kvPos++ }; kvPos++ + kv = append(kv, blob[kvPos:kvPos+nR*32]...) + + prefixHash := crypto.Keccak256(blob[:prefixEnd]) + rctBaseHash := crypto.Keccak256(blob[prefixEnd:rctBaseEnd]) + bpKvHash := crypto.Keccak256(kv) + + combined := make([]byte, 0, 96) + combined = append(combined, prefixHash...) + combined = append(combined, rctBaseHash...) + combined = append(combined, bpKvHash...) + return crypto.Keccak256(combined) +} + +// --- Deterministic RNG --- + +type deterministicRNG struct { + state []byte + count uint64 +} + +func newDeterministicRNG(seed []byte) *deterministicRNG { + h := sha3.NewLegacyKeccak256() + h.Write([]byte("monero_tx_rng")) + h.Write(seed) + return &deterministicRNG{state: h.Sum(nil)} +} + +func (r *deterministicRNG) Read(p []byte) (int, error) { + for i := 0; i < len(p); i += 32 { + h := sha3.NewLegacyKeccak256() + h.Write(r.state) + r.count++ + countBytes := make([]byte, 8) + binary.LittleEndian.PutUint64(countBytes, r.count) + h.Write(countBytes) + chunk := h.Sum(nil) + end := i + 32 + if end > len(p) { + end = len(p) + } + copy(p[i:end], chunk[:end-i]) + } + return len(p), nil +} diff --git a/chain/monero/client/client.go b/chain/monero/client/client.go new file mode 100644 index 00000000..b743cecd --- /dev/null +++ b/chain/monero/client/client.go @@ -0,0 +1,894 @@ +package client + +import ( + "bytes" + "context" + "encoding/hex" + "encoding/json" + "fmt" + "io" + "net/http" + "strings" + "time" + + xc "github.com/cordialsys/crosschain" + xcbuilder "github.com/cordialsys/crosschain/builder" + "github.com/cordialsys/crosschain/chain/monero/crypto" + "github.com/cordialsys/crosschain/chain/monero/tx_input" + xclient "github.com/cordialsys/crosschain/client" + "filippo.io/edwards25519" + txinfo "github.com/cordialsys/crosschain/client/tx_info" + xctypes "github.com/cordialsys/crosschain/client/types" + "github.com/cordialsys/crosschain/factory/signer" + "github.com/sirupsen/logrus" +) + +// txBatchSize is the number of transactions to fetch per RPC request. +// Public nodes limit requests in restricted mode. +const txBatchSize = 25 + +type Client struct { + url string + cfg *xc.ChainConfig + http *http.Client + lws *LWSClient // optional light wallet server for indexed queries +} + +func NewClient(cfg *xc.ChainConfig) (*Client, error) { + url, _ := cfg.ClientURL() + if url == "" { + return nil, fmt.Errorf("monero RPC URL not configured") + } + + c := &Client{ + url: url, + cfg: cfg, + http: &http.Client{ + Timeout: 30 * time.Second, + }, + } + + // If indexer_url is configured, use it as the LWS endpoint + if cfg.IndexerUrl != "" { + c.lws = NewLWSClient(cfg.IndexerUrl) + logrus.WithField("lws_url", cfg.IndexerUrl).Info("using monero-lws for indexed queries") + } + + return c, nil +} + +// jsonRPCRequest makes a JSON-RPC call to the Monero daemon +func (c *Client) jsonRPCRequest(ctx context.Context, method string, params interface{}) (json.RawMessage, error) { + reqBody := map[string]interface{}{ + "jsonrpc": "2.0", + "id": "0", + "method": method, + } + if params != nil { + reqBody["params"] = params + } + + bodyBytes, err := json.Marshal(reqBody) + if err != nil { + return nil, fmt.Errorf("failed to marshal request: %w", err) + } + + endpoint := strings.TrimRight(c.url, "/") + "/json_rpc" + req, err := http.NewRequestWithContext(ctx, "POST", endpoint, bytes.NewReader(bodyBytes)) + if err != nil { + return nil, fmt.Errorf("failed to create request: %w", err) + } + req.Header.Set("Content-Type", "application/json") + + resp, err := c.http.Do(req) + if err != nil { + return nil, fmt.Errorf("RPC request failed: %w", err) + } + defer resp.Body.Close() + + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("failed to read response: %w", err) + } + + var rpcResp struct { + Result json.RawMessage `json:"result"` + Error *struct { + Code int `json:"code"` + Message string `json:"message"` + } `json:"error"` + } + if err := json.Unmarshal(respBody, &rpcResp); err != nil { + return nil, fmt.Errorf("failed to parse RPC response: %w (body: %s)", err, string(respBody)) + } + if rpcResp.Error != nil { + return nil, fmt.Errorf("RPC error %d: %s", rpcResp.Error.Code, rpcResp.Error.Message) + } + + return rpcResp.Result, nil +} + +// httpRequest makes a direct HTTP request to a Monero daemon endpoint +func (c *Client) httpRequest(ctx context.Context, path string, params interface{}) (json.RawMessage, error) { + bodyBytes, err := json.Marshal(params) + if err != nil { + return nil, fmt.Errorf("failed to marshal request: %w", err) + } + + endpoint := strings.TrimRight(c.url, "/") + path + req, err := http.NewRequestWithContext(ctx, "POST", endpoint, bytes.NewReader(bodyBytes)) + if err != nil { + return nil, fmt.Errorf("failed to create request: %w", err) + } + req.Header.Set("Content-Type", "application/json") + + resp, err := c.http.Do(req) + if err != nil { + return nil, fmt.Errorf("HTTP request failed: %w", err) + } + defer resp.Body.Close() + + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("failed to read response: %w", err) + } + + return json.RawMessage(respBody), nil +} + +// getBlockCount returns the current block height +func (c *Client) getBlockCount(ctx context.Context) (uint64, error) { + result, err := c.jsonRPCRequest(ctx, "get_block_count", nil) + if err != nil { + return 0, err + } + var resp struct { + Count uint64 `json:"count"` + } + if err := json.Unmarshal(result, &resp); err != nil { + return 0, err + } + return resp.Count, nil +} + +// deriveWalletKeys loads the private key from env and derives the full key set. +// Returns (privateViewKey, publicSpendKey, error). +// The address parameter is used to verify we're scanning the right wallet (main address or subaddress). +func deriveWalletKeys() (privView, pubSpend []byte, err error) { + secret := signer.ReadPrivateKeyEnv() + if secret == "" { + return nil, nil, fmt.Errorf("XC_PRIVATE_KEY not set - required for Monero view key scanning") + } + + secretBz, err := hex.DecodeString(secret) + if err != nil { + return nil, nil, fmt.Errorf("failed to decode private key: %w", err) + } + + _, privViewKey, pubSpendKey, _, err := crypto.DeriveKeysFromSpend(secretBz) + if err != nil { + return nil, nil, fmt.Errorf("failed to derive keys: %w", err) + } + + return privViewKey, pubSpendKey, nil +} + +// defaultSubaddressCount is the number of subaddresses to precompute for scanning. +// An exchange would set this to the number of user addresses generated. +const defaultSubaddressCount = 100 + +// buildSubaddressMap precomputes subaddress spend keys for scanning. +func buildSubaddressMap(privView, pubSpend []byte, count uint32) map[crypto.SubaddressIndex][]byte { + subKeys := make(map[crypto.SubaddressIndex][]byte, count) + for i := uint32(1); i <= count; i++ { + idx := crypto.SubaddressIndex{Major: 0, Minor: i} + subSpend, _, err := crypto.DeriveSubaddressKeys(privView, pubSpend, idx) + if err != nil { + continue + } + subKeys[idx] = subSpend + } + return subKeys +} + +// moneroTxJson represents the parsed JSON of a Monero transaction +type moneroTxJson struct { + Version int `json:"version"` + UnlockTime uint64 `json:"unlock_time"` + Vin []struct { + Key struct { + Amount uint64 `json:"amount"` + KeyOffsets []uint64 `json:"key_offsets"` + KImage string `json:"k_image"` + } `json:"key"` + } `json:"vin"` + Vout []struct { + Amount uint64 `json:"amount"` + Target struct { + TaggedKey struct { + Key string `json:"key"` + ViewTag string `json:"view_tag"` + } `json:"tagged_key"` + Key string `json:"key"` + } `json:"target"` + } `json:"vout"` + Extra []int `json:"extra"` + RctSignatures struct { + Type int `json:"type"` + TxnFee uint64 `json:"txnFee"` + EcdhInfo []struct { + Amount string `json:"amount"` + } `json:"ecdhInfo"` + } `json:"rct_signatures"` +} + +// getOutputKey extracts the output one-time public key from a transaction output +func getOutputKey(vout struct { + Amount uint64 `json:"amount"` + Target struct { + TaggedKey struct { + Key string `json:"key"` + ViewTag string `json:"view_tag"` + } `json:"tagged_key"` + Key string `json:"key"` + } `json:"target"` +}) string { + if vout.Target.TaggedKey.Key != "" { + return vout.Target.TaggedKey.Key + } + return vout.Target.Key +} + +// scanTransaction scans a single transaction for outputs belonging to the given wallet, +// including both the main address and all precomputed subaddresses. +// Returns the total amount received in this transaction. +func scanTransaction(txJsonStr string, privateViewKey, publicSpendKey []byte, subKeys map[crypto.SubaddressIndex][]byte) (uint64, error) { + var txJson moneroTxJson + if err := json.Unmarshal([]byte(txJsonStr), &txJson); err != nil { + return 0, fmt.Errorf("failed to parse tx JSON: %w", err) + } + + // Extract tx public key from extra field + extraBytes := make([]byte, len(txJson.Extra)) + for i, v := range txJson.Extra { + extraBytes[i] = byte(v) + } + txPubKey, err := crypto.ParseTxPubKey(extraBytes) + if err != nil { + logrus.WithError(err).Debug("failed to parse tx pub key from extra") + return 0, nil + } + + var totalReceived uint64 + for outputIdx, vout := range txJson.Vout { + outputKey := getOutputKey(vout) + if outputKey == "" { + continue + } + + // Get encrypted amount from ecdh info + var encryptedAmount string + if outputIdx < len(txJson.RctSignatures.EcdhInfo) { + encryptedAmount = txJson.RctSignatures.EcdhInfo[outputIdx].Amount + } + + // Scan against main address + all subaddresses + matched, matchedIdx, amount, err := crypto.ScanOutputForSubaddresses( + txPubKey, + uint64(outputIdx), + outputKey, + encryptedAmount, + privateViewKey, + publicSpendKey, + subKeys, + ) + if err != nil { + logrus.WithError(err).WithField("output_index", outputIdx).Debug("error scanning output") + continue + } + if matched { + logrus.WithFields(logrus.Fields{ + "output_index": outputIdx, + "amount": amount, + "subaddress_major": matchedIdx.Major, + "subaddress_minor": matchedIdx.Minor, + }).Info("found owned output") + totalReceived += amount + } + } + + return totalReceived, nil +} + +func (c *Client) FetchTransferInput(ctx context.Context, args xcbuilder.TransferArgs) (xc.TxInput, error) { + input := tx_input.NewTxInput() + + blockCount, err := c.getBlockCount(ctx) + if err != nil { + return nil, fmt.Errorf("failed to get block count: %w", err) + } + input.BlockHeight = blockCount + + // Get fee estimation via JSON-RPC + feeResult, err := c.jsonRPCRequest(ctx, "get_fee_estimate", nil) + if err != nil { + logrus.WithError(err).Warn("failed to get fee estimate, using default") + input.PerByteFee = 20000 + } else { + var feeEstimate struct { + Fee uint64 `json:"fee"` + QuantizationMask uint64 `json:"quantization_mask"` + } + if err := json.Unmarshal(feeResult, &feeEstimate); err != nil { + logrus.WithError(err).Warn("failed to parse fee estimate") + input.PerByteFee = 20000 + } else { + input.PerByteFee = feeEstimate.Fee + input.QuantizationMask = feeEstimate.QuantizationMask + logrus.WithFields(logrus.Fields{ + "fee_per_byte": feeEstimate.Fee, + "quantization_mask": feeEstimate.QuantizationMask, + }).Info("fee estimate") + } + } + + // Populate spendable outputs: use LWS if available, otherwise scan blocks + from := args.GetFrom() + if c.lws != nil { + if err := c.populateFromLWS(ctx, input, from); err != nil { + logrus.WithError(err).Warn("LWS query failed, falling back to block scan") + if err := c.PopulateTransferInput(ctx, input, from); err != nil { + return nil, fmt.Errorf("failed to find spendable outputs: %w", err) + } + } + } else { + if err := c.PopulateTransferInput(ctx, input, from); err != nil { + return nil, fmt.Errorf("failed to find spendable outputs: %w", err) + } + } + + return input, nil +} + +// populateFromLWS fetches spendable outputs from the Light Wallet Server. +// Instant - no block scanning needed. +func (c *Client) populateFromLWS(ctx context.Context, input *tx_input.TxInput, from xc.Address) error { + // Derive view key for LWS auth + privView, _, err := deriveWalletKeys() + if err != nil { + return fmt.Errorf("cannot derive view key: %w", err) + } + + // Set LWS credentials and login + c.lws.SetCredentials(string(from), hex.EncodeToString(privView)) + if err := c.lws.Login(ctx); err != nil { + return fmt.Errorf("LWS login failed: %w", err) + } + + // Fetch unspent outputs + outputs, perByteFee, feeMask, err := c.lws.GetUnspentOuts(ctx) + if err != nil { + return fmt.Errorf("get_unspent_outs failed: %w", err) + } + + // Use LWS fee estimates if available + if perByteFee > 0 { + input.PerByteFee = perByteFee + } + if feeMask > 0 { + input.QuantizationMask = feeMask + } + + // Set RNG seed + rngSeedData := append(privView, crypto.VarIntEncode(input.BlockHeight)...) + input.RngSeed = crypto.Keccak256(rngSeedData) + + // Convert LWS outputs to tx_input format + converted := ConvertLWSOutputs(outputs, privView) + + // Fetch decoys for each output + for i := range converted { + out := &converted[i] + if out.GlobalIndex == 0 { + continue + } + + decoys, err := c.FetchDecoys(ctx, out.GlobalIndex, ringSize-1) + if err != nil { + logrus.WithError(err).WithField("tx_hash", out.TxHash).Warn("failed to fetch decoys") + continue + } + + if len(decoys) < 15 { + logrus.WithField("tx_hash", out.TxHash).Warn("insufficient decoys") + continue + } + + var ringMembers []tx_input.RingMember + for _, d := range decoys { + ringMembers = append(ringMembers, tx_input.RingMember{ + GlobalIndex: d.GlobalIndex, + PublicKey: d.PublicKey, + Commitment: d.Commitment, + }) + } + out.RingMembers = ringMembers + } + + // Filter outputs with enough ring members + for _, out := range converted { + if len(out.RingMembers) >= 15 { + input.Outputs = append(input.Outputs, out) + } + } + + if len(input.Outputs) == 0 { + return fmt.Errorf("no spendable outputs with sufficient decoys found via LWS") + } + + logrus.WithField("spendable", len(input.Outputs)).Info("populated outputs from LWS") + return nil +} + +func (c *Client) FetchBalance(ctx context.Context, args *xclient.BalanceArgs) (xc.AmountBlockchain, error) { + address := args.Address() + if address == "" { + return xc.NewAmountBlockchainFromUint64(0), fmt.Errorf("address is required") + } + + // Use LWS for instant balance if available + if c.lws != nil { + privView, _, err := deriveWalletKeys() + if err == nil { + c.lws.SetCredentials(string(address), hex.EncodeToString(privView)) + if err := c.lws.Login(ctx); err == nil { + info, err := c.lws.GetAddressInfo(ctx) + if err == nil { + var received, sent uint64 + fmt.Sscanf(info.TotalReceived, "%d", &received) + fmt.Sscanf(info.TotalSent, "%d", &sent) + balance := received - sent + logrus.WithFields(logrus.Fields{ + "received": received, + "sent": sent, + "balance": balance, + "scanned": info.ScannedHeight, + }).Info("balance from LWS") + return xc.NewAmountBlockchainFromUint64(balance), nil + } + logrus.WithError(err).Warn("LWS balance failed, falling back to scan") + } + } + } + + // Fallback: scan blocks + privView, pubSpend, err := deriveWalletKeys() + if err != nil { + logrus.WithError(err).Warn("cannot derive view key for balance scanning") + return xc.NewAmountBlockchainFromUint64(0), nil + } + + // Precompute subaddress spend keys for scanning + subKeys := buildSubaddressMap(privView, pubSpend, defaultSubaddressCount) + + blockCount, err := c.getBlockCount(ctx) + if err != nil { + return xc.NewAmountBlockchainFromUint64(0), fmt.Errorf("failed to get block count: %w", err) + } + + // Scan the last 200 blocks for outputs belonging to us. + // This is a practical limit for detecting recent deposits. + // A full wallet scan would require scanning from genesis. + scanDepth := uint64(1000) + startHeight := blockCount - scanDepth + if startHeight > blockCount { // underflow check + startHeight = 0 + } + + logrus.WithFields(logrus.Fields{ + "start_height": startHeight, + "end_height": blockCount, + "scan_depth": scanDepth, + }).Info("scanning blocks for Monero outputs") + + var totalBalance uint64 + + // Scan blocks in batches + for height := startHeight; height < blockCount; height++ { + // Get block at this height + blockResult, err := c.jsonRPCRequest(ctx, "get_block", map[string]interface{}{ + "height": height, + }) + if err != nil { + logrus.WithError(err).WithField("height", height).Debug("failed to get block") + continue + } + + var block struct { + BlockHeader struct { + Height uint64 `json:"height"` + } `json:"block_header"` + Json string `json:"json"` + TxHashes []string `json:"tx_hashes"` + } + if err := json.Unmarshal(blockResult, &block); err != nil { + continue + } + + if len(block.TxHashes) == 0 { + continue + } + + // Fetch transactions in batches (public nodes limit requests in restricted mode) + for batchStart := 0; batchStart < len(block.TxHashes); batchStart += txBatchSize { + batchEnd := batchStart + txBatchSize + if batchEnd > len(block.TxHashes) { + batchEnd = len(block.TxHashes) + } + batch := block.TxHashes[batchStart:batchEnd] + + txParams := map[string]interface{}{ + "txs_hashes": batch, + "decode_as_json": true, + } + txResult, err := c.httpRequest(ctx, "/get_transactions", txParams) + if err != nil { + logrus.WithError(err).WithField("height", height).Debug("failed to get transactions") + continue + } + + var txResp struct { + Txs []struct { + AsJson string `json:"as_json"` + TxHash string `json:"tx_hash"` + } `json:"txs"` + Status string `json:"status"` + } + if err := json.Unmarshal(txResult, &txResp); err != nil { + continue + } + if txResp.Status != "OK" { + logrus.WithField("status", txResp.Status).WithField("height", height).Debug("get_transactions returned non-OK status") + continue + } + + for _, tx := range txResp.Txs { + if tx.AsJson == "" { + continue + } + amount, err := scanTransaction(tx.AsJson, privView, pubSpend, subKeys) + if err != nil { + logrus.WithError(err).WithField("tx_hash", tx.TxHash).Debug("error scanning transaction") + continue + } + if amount > 0 { + logrus.WithFields(logrus.Fields{ + "tx_hash": tx.TxHash, + "amount": amount, + "height": height, + }).Info("found incoming transfer") + totalBalance += amount + } + } + } + } + + logrus.WithField("total_balance", totalBalance).Info("scan complete") + return xc.NewAmountBlockchainFromUint64(totalBalance), nil +} + +func (c *Client) FetchDecimals(ctx context.Context, contract xc.ContractAddress) (int, error) { + return 12, nil +} + +func (c *Client) SubmitTx(ctx context.Context, submitReq xctypes.SubmitTxReq) error { + txData := submitReq.TxData + if len(txData) == 0 { + return fmt.Errorf("empty transaction data") + } + + txHex := hex.EncodeToString(txData) + + // Submit via LWS too (so it tracks our key images for spent detection) + if c.lws != nil { + _, err := c.lws.post(ctx, "submit_raw_tx", map[string]interface{}{ + "tx": txHex, + }) + if err != nil { + logrus.WithError(err).Warn("LWS submit failed, submitting to daemon only") + } + } + + params := map[string]interface{}{ + "tx_as_hex": txHex, + "do_not_relay": false, + } + + result, err := c.httpRequest(ctx, "/send_raw_transaction", params) + if err != nil { + return fmt.Errorf("failed to submit transaction: %w", err) + } + + var submitResult struct { + Status string `json:"status"` + Reason string `json:"reason"` + DoubleSpend bool `json:"double_spend"` + FeeTooLow bool `json:"fee_too_low"` + InvalidInput bool `json:"invalid_input"` + InvalidOutput bool `json:"invalid_output"` + LowMixin bool `json:"low_mixin"` + NotRelayed bool `json:"not_relayed"` + Overspend bool `json:"overspend"` + TooBig bool `json:"too_big"` + TooFewOutputs bool `json:"too_few_outputs"` + SanityCheckFailed bool `json:"sanity_check_failed"` + } + if err := json.Unmarshal(result, &submitResult); err != nil { + return fmt.Errorf("failed to parse submit result: %w (raw: %s)", err, string(result)) + } + if submitResult.Status != "OK" { + logrus.WithFields(logrus.Fields{ + "status": submitResult.Status, + "reason": submitResult.Reason, + "double_spend": submitResult.DoubleSpend, + "fee_too_low": submitResult.FeeTooLow, + "invalid_input": submitResult.InvalidInput, + "invalid_output":submitResult.InvalidOutput, + "low_mixin": submitResult.LowMixin, + "overspend": submitResult.Overspend, + "too_big": submitResult.TooBig, + "sanity_failed": submitResult.SanityCheckFailed, + }).Error("transaction rejected by node") + return fmt.Errorf("transaction rejected: %s (status: %s)", submitResult.Reason, submitResult.Status) + } + + return nil +} + +func (c *Client) FetchTxInfo(ctx context.Context, args *txinfo.Args) (txinfo.TxInfo, error) { + hash := args.TxHash() + + params := map[string]interface{}{ + "txs_hashes": []string{string(hash)}, + "decode_as_json": true, + } + + result, err := c.httpRequest(ctx, "/get_transactions", params) + if err != nil { + return txinfo.TxInfo{}, fmt.Errorf("failed to fetch transaction: %w", err) + } + + var txResult struct { + Txs []struct { + AsHex string `json:"as_hex"` + AsJson string `json:"as_json"` + BlockHeight uint64 `json:"block_height"` + BlockTimestamp uint64 `json:"block_timestamp"` + TxHash string `json:"tx_hash"` + InPool bool `json:"in_pool"` + OutputIndices []uint64 `json:"output_indices"` + } `json:"txs"` + Status string `json:"status"` + MissedTx []string `json:"missed_tx"` + } + if err := json.Unmarshal(result, &txResult); err != nil { + return txinfo.TxInfo{}, fmt.Errorf("failed to parse transaction data: %w", err) + } + if txResult.Status != "OK" { + return txinfo.TxInfo{}, fmt.Errorf("get_transactions returned status: %s", txResult.Status) + } + if len(txResult.MissedTx) > 0 { + return txinfo.TxInfo{}, fmt.Errorf("transaction not found: %s", hash) + } + if len(txResult.Txs) == 0 { + return txinfo.TxInfo{}, fmt.Errorf("no transaction data returned for: %s", hash) + } + + txData := txResult.Txs[0] + + blockCount, err := c.getBlockCount(ctx) + if err != nil { + return txinfo.TxInfo{}, fmt.Errorf("failed to get block count: %w", err) + } + + var confirmations uint64 + if !txData.InPool { + confirmations = blockCount - txData.BlockHeight + } + + // Parse fee from tx JSON + var txJson struct { + RctSignatures struct { + TxnFee uint64 `json:"txnFee"` + } `json:"rct_signatures"` + } + if txData.AsJson != "" { + if err := json.Unmarshal([]byte(txData.AsJson), &txJson); err != nil { + logrus.WithError(err).Warn("failed to parse transaction JSON") + } + } + + fee := xc.NewAmountBlockchainFromUint64(txJson.RctSignatures.TxnFee) + + // Build TxInfo using library constructors + block := txinfo.NewBlock(xc.XMR, txData.BlockHeight, "", time.Unix(int64(txData.BlockTimestamp), 0)) + info := txinfo.NewTxInfo(block, c.cfg.GetChain(), string(hash), confirmations, nil) + info.Fees = []*txinfo.Balance{ + txinfo.NewBalance(xc.XMR, xc.ContractAddress(xc.XMR), fee, nil), + } + + // Decode outputs using the fixed view key (no private spend key needed). + // This finds ALL outputs sent to any address sharing our view key, + // and recovers the recipient address for each. + if txData.AsJson != "" { + privView := crypto.FixedPrivateViewKey + addrPrefix := crypto.MainnetAddressPrefix + if c.cfg != nil && (string(c.cfg.ChainID) == "testnet" || c.cfg.Network == "testnet") { + addrPrefix = crypto.TestnetAddressPrefix + } + outputs := scanTransactionViewKeyOnly(txData.AsJson, privView, addrPrefix) + if len(outputs) > 0 { + // Native XMR transfer: empty contract = native asset + mv := txinfo.NewMovement(xc.XMR, "") + + // From: total spent (sum of outputs + fee), sender hidden by ring sigs + var totalOut uint64 + for _, out := range outputs { + totalOut += out.amount + } + mv.AddSource("", xc.NewAmountBlockchainFromUint64(totalOut+txJson.RctSignatures.TxnFee), nil) + + // To: each decoded output with its recovered address + for _, out := range outputs { + mv.AddDestination(out.address, xc.NewAmountBlockchainFromUint64(out.amount), nil) + } + + info.Movements = append(info.Movements, mv) + } + } + + return *info, nil +} + +func (c *Client) FetchLegacyTxInfo(ctx context.Context, hash xc.TxHash) (txinfo.LegacyTxInfo, error) { + args := txinfo.NewArgs(hash) + info, err := c.FetchTxInfo(ctx, args) + if err != nil { + return txinfo.LegacyTxInfo{}, err + } + return txinfo.LegacyTxInfo{ + TxID: info.Hash, + Confirmations: int64(info.Confirmations), + }, nil +} + +func (c *Client) FetchBlock(ctx context.Context, args *xclient.BlockArgs) (*txinfo.BlockWithTransactions, error) { + var result json.RawMessage + var err error + + height, hasHeight := args.Height() + if hasHeight { + result, err = c.jsonRPCRequest(ctx, "get_block", map[string]interface{}{ + "height": height, + }) + } else { + result, err = c.jsonRPCRequest(ctx, "get_last_block_header", nil) + } + if err != nil { + return nil, fmt.Errorf("failed to fetch block: %w", err) + } + + var blockResult struct { + BlockHeader struct { + Height uint64 `json:"height"` + Timestamp uint64 `json:"timestamp"` + Hash string `json:"hash"` + } `json:"block_header"` + } + if err := json.Unmarshal(result, &blockResult); err != nil { + return nil, fmt.Errorf("failed to parse block: %w", err) + } + + header := blockResult.BlockHeader + block := txinfo.NewBlock(xc.XMR, header.Height, header.Hash, time.Unix(int64(header.Timestamp), 0)) + + return &txinfo.BlockWithTransactions{ + Block: *block, + }, nil +} + +type decodedOutput struct { + amount uint64 + address xc.Address +} + +// scanTransactionViewKeyOnly decodes output amounts using only the private view key, +// then matches each output against known spend keys to determine the recipient address. +// This is how an exchange scans for deposits across all user addresses. +func scanTransactionViewKeyOnly(txJsonStr string, privateViewKey []byte, addressPrefix byte) []decodedOutput { + var txJson moneroTxJson + if err := json.Unmarshal([]byte(txJsonStr), &txJson); err != nil { + return nil + } + + extraBytes := make([]byte, len(txJson.Extra)) + for i, v := range txJson.Extra { + extraBytes[i] = byte(v) + } + txPubKey, err := crypto.ParseTxPubKey(extraBytes) + if err != nil { + return nil + } + + derivation, err := crypto.GenerateKeyDerivation(txPubKey, privateViewKey) + if err != nil { + return nil + } + + // Compute the public view key from the private view key + pubView, _ := crypto.PublicFromPrivate(privateViewKey) + + var results []decodedOutput + for outputIdx, vout := range txJson.Vout { + outputKey := getOutputKey(vout) + + var encAmount string + if outputIdx < len(txJson.RctSignatures.EcdhInfo) { + encAmount = txJson.RctSignatures.EcdhInfo[outputIdx].Amount + } + if encAmount == "" { + continue + } + + scalar, err := crypto.DerivationToScalar(derivation, uint64(outputIdx)) + if err != nil { + continue + } + + amount, err := crypto.DecryptAmount(encAmount, scalar) + if err != nil { + continue + } + + if amount == 0 || amount >= 1000000000000000000 { + continue + } + + // Derive the expected public spend key: pubSpend = P - H_s(D||idx)*G + // If we can recover a valid spend key, we can reconstruct the full address. + addr := recoverAddress(outputKey, scalar, pubView, addressPrefix) + + results = append(results, decodedOutput{amount: amount, address: addr}) + } + + return results +} + +// recoverAddress recovers the recipient's Monero address from an output key. +// Given output key P and derivation scalar s: pubSpend = P - s*G +// Then address = base58(prefix || pubSpend || pubView || checksum) +func recoverAddress(outputKeyHex string, scalar []byte, pubView []byte, prefix byte) xc.Address { + outputKeyBytes, err := hex.DecodeString(outputKeyHex) + if err != nil || len(outputKeyBytes) != 32 { + return "" + } + + P, err := edwards25519.NewIdentityPoint().SetBytes(outputKeyBytes) + if err != nil { + return "" + } + + sScalar, err := edwards25519.NewScalar().SetCanonicalBytes(scalar) + if err != nil { + return "" + } + + // pubSpend = P - s*G + sG := edwards25519.NewGeneratorPoint().ScalarBaseMult(sScalar) + negSG := edwards25519.NewIdentityPoint().Negate(sG) + pubSpend := edwards25519.NewIdentityPoint().Add(P, negSG) + + return xc.Address(crypto.GenerateAddressWithPrefix(prefix, pubSpend.Bytes(), pubView)) +} + +var _ xclient.Client = &Client{} diff --git a/chain/monero/client/decoys.go b/chain/monero/client/decoys.go new file mode 100644 index 00000000..dd452e0d --- /dev/null +++ b/chain/monero/client/decoys.go @@ -0,0 +1,193 @@ +package client + +import ( + "context" + "encoding/json" + "fmt" + "math/big" + "sort" + + "crypto/rand" + + "github.com/sirupsen/logrus" +) + +const ( + // ringSize is the number of ring members per input (1 real + 15 decoys) + ringSize = 16 +) + +// DecoyOutput represents a decoy output fetched from the blockchain +type DecoyOutput struct { + GlobalIndex uint64 + PublicKey string // hex + Commitment string // hex (rct commitment) +} + +// FetchDecoys selects decoy ring members for a transaction input. +// It picks random outputs from the blockchain distribution, avoiding the real output. +func (c *Client) FetchDecoys(ctx context.Context, realGlobalIndex uint64, count int) ([]DecoyOutput, error) { + // Get total output count from get_info (lightweight, no huge distribution array) + infoResult, err := c.jsonRPCRequest(ctx, "get_info", nil) + if err != nil { + return nil, fmt.Errorf("failed to get info: %w", err) + } + var info struct { + TxCount uint64 `json:"tx_count"` + Height uint64 `json:"height"` + } + if err := json.Unmarshal(infoResult, &info); err != nil { + return nil, fmt.Errorf("failed to parse info: %w", err) + } + + // Use the real output's global index as the upper bound. + // Decoys should be from outputs that exist (index <= our output's index). + totalOutputs := realGlobalIndex + if totalOutputs < uint64(count+1) { + // On a very new chain, use tx count estimate + totalOutputs = info.TxCount * 2 + } + if totalOutputs < uint64(count+1) { + return nil, fmt.Errorf("not enough outputs on chain for ring size %d", count) + } + + // Select random global indices using gamma distribution (Monero's approach) + // Simplified: uniform random selection weighted toward recent outputs + selectedIndices := selectDecoyIndices(totalOutputs, realGlobalIndex, count) + + // Fetch the output data for selected indices + outs, err := c.fetchOutputs(ctx, selectedIndices) + if err != nil { + return nil, fmt.Errorf("failed to fetch decoy outputs: %w", err) + } + + return outs, nil +} + +// selectDecoyIndices picks random output indices, avoiding the real output. +// Uses a simplified version of Monero's gamma distribution for recent-output bias. +func selectDecoyIndices(totalOutputs uint64, realIndex uint64, count int) []uint64 { + selected := make(map[uint64]bool) + selected[realIndex] = true // avoid picking the real output as decoy + + indices := make([]uint64, 0, count) + maxAttempts := count * 20 + + for len(indices) < count && maxAttempts > 0 { + maxAttempts-- + + // Gamma-like distribution: bias toward recent outputs + // Use rejection sampling with a simple triangular distribution + randBytes := make([]byte, 8) + rand.Read(randBytes) + r := new(big.Int).SetBytes(randBytes) + r.Mod(r, new(big.Int).SetUint64(totalOutputs)) + idx := r.Uint64() + + // Bias toward recent: with 50% chance, pick from last 25% of outputs + coin := make([]byte, 1) + rand.Read(coin) + if coin[0] < 128 && totalOutputs > 100 { + recentStart := totalOutputs - totalOutputs/4 + rand.Read(randBytes) + r2 := new(big.Int).SetBytes(randBytes) + r2.Mod(r2, new(big.Int).SetUint64(totalOutputs/4)) + idx = recentStart + r2.Uint64() + } + + if idx == 0 { + idx = 1 + } + if idx >= totalOutputs { + idx = totalOutputs - 1 + } + + if !selected[idx] { + selected[idx] = true + indices = append(indices, idx) + } + } + + sort.Slice(indices, func(i, j int) bool { return indices[i] < indices[j] }) + return indices +} + +// fetchOutputs retrieves output data (public key and commitment) for given global indices. +func (c *Client) fetchOutputs(ctx context.Context, indices []uint64) ([]DecoyOutput, error) { + getOuts := make([]map[string]uint64, len(indices)) + for i, idx := range indices { + getOuts[i] = map[string]uint64{"amount": 0, "index": idx} + } + + result, err := c.httpRequest(ctx, "/get_outs", map[string]interface{}{ + "outputs": getOuts, + "get_txid": false, + }) + if err != nil { + return nil, fmt.Errorf("get_outs failed: %w", err) + } + + var outsResp struct { + Outs []struct { + Key string `json:"key"` + Mask string `json:"mask"` + Txid string `json:"txid"` + Height uint64 `json:"height"` + } `json:"outs"` + Status string `json:"status"` + } + if err := json.Unmarshal(result, &outsResp); err != nil { + return nil, fmt.Errorf("failed to parse get_outs response: %w", err) + } + if outsResp.Status != "OK" { + return nil, fmt.Errorf("get_outs returned status: %s", outsResp.Status) + } + + decoys := make([]DecoyOutput, len(outsResp.Outs)) + for i, out := range outsResp.Outs { + decoys[i] = DecoyOutput{ + GlobalIndex: indices[i], + PublicKey: out.Key, + Commitment: out.Mask, + } + } + + logrus.WithField("count", len(decoys)).Debug("fetched decoy outputs") + return decoys, nil +} + +// BuildRing constructs a sorted ring of outputs for CLSAG signing. +// Returns the ring (sorted by global index), the position of the real output, and relative key offsets. +// See also: builder.buildRingFromMembers which does the same for tx_input.RingMember types. +func BuildRing(realIndex uint64, realKey string, realCommitment string, decoys []DecoyOutput) (ring []DecoyOutput, realPos int, keyOffsets []uint64) { + // Combine real output with decoys + all := make([]DecoyOutput, 0, len(decoys)+1) + all = append(all, DecoyOutput{ + GlobalIndex: realIndex, + PublicKey: realKey, + Commitment: realCommitment, + }) + all = append(all, decoys...) + + // Sort by global index + sort.Slice(all, func(i, j int) bool { return all[i].GlobalIndex < all[j].GlobalIndex }) + + // Find real output position after sorting + realPos = -1 + for i, out := range all { + if out.GlobalIndex == realIndex { + realPos = i + break + } + } + + // Compute relative key offsets (each offset is relative to the previous) + keyOffsets = make([]uint64, len(all)) + var prev uint64 + for i, out := range all { + keyOffsets[i] = out.GlobalIndex - prev + prev = out.GlobalIndex + } + + return all, realPos, keyOffsets +} diff --git a/chain/monero/client/lws.go b/chain/monero/client/lws.go new file mode 100644 index 00000000..060d6300 --- /dev/null +++ b/chain/monero/client/lws.go @@ -0,0 +1,232 @@ +package client + +import ( + "bytes" + "context" + "encoding/hex" + "encoding/json" + "fmt" + "io" + "net/http" + "time" + + "github.com/cordialsys/crosschain/chain/monero/crypto" + "github.com/cordialsys/crosschain/chain/monero/tx_input" + "github.com/sirupsen/logrus" +) + +// LWSClient communicates with a monero-lws (Light Wallet Server) instance. +// It provides indexed output queries so we don't need to scan the blockchain. +type LWSClient struct { + url string + http *http.Client + // The main (standard) address registered with the LWS + address string + // The private view key (hex) for authentication + viewKey string +} + +// NewLWSClient creates a new LWS client from an indexer URL. +func NewLWSClient(url string) *LWSClient { + return &LWSClient{ + url: url, + http: &http.Client{ + Timeout: 30 * time.Second, + }, + } +} + +// SetCredentials sets the address and view key for LWS API authentication. +func (l *LWSClient) SetCredentials(address, viewKeyHex string) { + l.address = address + l.viewKey = viewKeyHex +} + +// post makes an HTTP POST request to the LWS endpoint. +func (l *LWSClient) post(ctx context.Context, endpoint string, body interface{}) (json.RawMessage, error) { + bodyBytes, err := json.Marshal(body) + if err != nil { + return nil, fmt.Errorf("marshal request: %w", err) + } + + req, err := http.NewRequestWithContext(ctx, "POST", l.url+"/"+endpoint, bytes.NewReader(bodyBytes)) + if err != nil { + return nil, fmt.Errorf("create request: %w", err) + } + req.Header.Set("Content-Type", "application/json") + + resp, err := l.http.Do(req) + if err != nil { + return nil, fmt.Errorf("LWS request failed: %w", err) + } + defer resp.Body.Close() + + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("read response: %w", err) + } + + if resp.StatusCode != 200 { + return nil, fmt.Errorf("LWS %s returned %d: %s", endpoint, resp.StatusCode, string(respBody)) + } + + return json.RawMessage(respBody), nil +} + +// Login registers the address with the LWS if needed. +func (l *LWSClient) Login(ctx context.Context) error { + result, err := l.post(ctx, "login", map[string]interface{}{ + "address": l.address, + "view_key": l.viewKey, + "create_account": true, + "generated_locally": true, + }) + if err != nil { + return fmt.Errorf("login failed: %w", err) + } + + var resp struct { + NewAddress bool `json:"new_address"` + StartHeight uint64 `json:"start_height"` + } + if err := json.Unmarshal(result, &resp); err != nil { + return fmt.Errorf("parse login response: %w", err) + } + + if resp.NewAddress { + logrus.WithField("start_height", resp.StartHeight).Info("registered new address with LWS") + } + return nil +} + +// LWSOutput represents an output from the get_unspent_outs response. +type LWSOutput struct { + Amount json.Number `json:"amount"` + Index uint16 `json:"index"` + GlobalIndex json.Number `json:"global_index"` + TxHash string `json:"tx_hash"` + TxPubKey string `json:"tx_pub_key"` + PublicKey string `json:"public_key"` + Rct string `json:"rct"` + Height uint64 `json:"height"` + Recipient *struct { + MajI uint32 `json:"maj_i"` + MinI uint32 `json:"min_i"` + } `json:"recipient,omitempty"` + SpendKeyImages []string `json:"spend_key_images"` +} + +// GetUnspentOuts fetches spendable outputs from the LWS. +func (l *LWSClient) GetUnspentOuts(ctx context.Context) ([]LWSOutput, uint64, uint64, error) { + result, err := l.post(ctx, "get_unspent_outs", map[string]interface{}{ + "address": l.address, + "view_key": l.viewKey, + "amount": "0", + "mixin": 15, + "use_dust": true, + "dust_threshold": "0", + }) + if err != nil { + return nil, 0, 0, err + } + + var resp struct { + Outputs []LWSOutput `json:"outputs"` + Amount string `json:"amount"` + PerByteFee uint64 `json:"per_byte_fee"` + FeeMask uint64 `json:"fee_mask"` + Fees []uint64 `json:"fees"` + } + if err := json.Unmarshal(result, &resp); err != nil { + return nil, 0, 0, fmt.Errorf("parse unspent outs: %w", err) + } + + perByteFee := resp.PerByteFee + feeMask := resp.FeeMask + + // Filter outputs that LWS already knows are spent (have valid key image) + var unspent []LWSOutput + for _, out := range resp.Outputs { + if len(out.SpendKeyImages) > 0 && len(out.SpendKeyImages[0]) == 64 { + // LWS has a key image for this output - it's been spent + logrus.WithField("global_index", out.GlobalIndex).Debug("LWS reports output as spent (has key image)") + continue + } + unspent = append(unspent, out) + } + + logrus.WithFields(logrus.Fields{ + "total": len(resp.Outputs), + "unspent": len(unspent), + "per_byte_fee": perByteFee, + "fee_mask": feeMask, + }).Info("got unspent outputs from LWS") + + return unspent, perByteFee, feeMask, nil +} + +// GetAddressInfo fetches balance info from the LWS. +type LWSAddressInfo struct { + TotalReceived string `json:"total_received"` + TotalSent string `json:"total_sent"` + LockedFunds string `json:"locked_funds"` + ScannedHeight uint64 `json:"scanned_height"` + BlockchainHeight uint64 `json:"blockchain_height"` +} + +func (l *LWSClient) GetAddressInfo(ctx context.Context) (*LWSAddressInfo, error) { + result, err := l.post(ctx, "get_address_info", map[string]interface{}{ + "address": l.address, + "view_key": l.viewKey, + }) + if err != nil { + return nil, err + } + + var resp LWSAddressInfo + if err := json.Unmarshal(result, &resp); err != nil { + return nil, fmt.Errorf("parse address info: %w", err) + } + return &resp, nil +} + +// ConvertLWSOutputs converts LWS outputs to the tx_input.Output format used by the builder. +func ConvertLWSOutputs(outputs []LWSOutput, privateViewKey []byte) []tx_input.Output { + var result []tx_input.Output + + for _, out := range outputs { + amount, _ := out.Amount.Int64() + globalIdx, _ := out.GlobalIndex.Int64() + + // Extract commitment from rct field (first 64 hex chars = 32 bytes) + commitment := "" + if len(out.Rct) >= 64 { + commitment = out.Rct[:64] + } + + // Compute commitment mask from view key + tx pub key + txPubKeyBytes, _ := hex.DecodeString(out.TxPubKey) + commitmentMask := "" + if len(txPubKeyBytes) == 32 && len(privateViewKey) == 32 { + derivation, err := crypto.GenerateKeyDerivation(txPubKeyBytes, privateViewKey) + if err == nil { + scalar, _ := crypto.DerivationToScalar(derivation, uint64(out.Index)) + maskData := append([]byte(crypto.CommitmentMaskLabel), scalar...) + commitmentMask = hex.EncodeToString(crypto.ScReduce32(crypto.Keccak256(maskData))) + } + } + + result = append(result, tx_input.Output{ + Amount: uint64(amount), + Index: uint64(out.Index), + TxHash: out.TxHash, + GlobalIndex: uint64(globalIdx), + PublicKey: out.PublicKey, + Commitment: commitment, + TxPubKey: out.TxPubKey, + CommitmentMask: commitmentMask, + }) + } + + return result +} diff --git a/chain/monero/client/scan.go b/chain/monero/client/scan.go new file mode 100644 index 00000000..c422904d --- /dev/null +++ b/chain/monero/client/scan.go @@ -0,0 +1,424 @@ +package client + +import ( + "context" + "encoding/hex" + "encoding/json" + "fmt" + + xc "github.com/cordialsys/crosschain" + "github.com/cordialsys/crosschain/chain/monero/crypto" + "github.com/cordialsys/crosschain/chain/monero/tx_input" + "github.com/cordialsys/crosschain/factory/signer" + "github.com/sirupsen/logrus" + "filippo.io/edwards25519" +) + +// OwnedOutput represents an output that belongs to our wallet, with all the +// information needed to spend it. +type OwnedOutput struct { + Amount uint64 + TxHash string + OutputIndex uint64 + GlobalIndex uint64 // populated later from get_outs + PublicKey string // hex, the one-time output key + Commitment string // hex, the Pedersen commitment + TxPubKey string // hex, the transaction public key R (needed for spending) + // The derivation scalar needed to compute the one-time private key for spending + DerivationScalar []byte + // Which subaddress this output was sent to + SubaddressIndex crypto.SubaddressIndex +} + +// ScanBlocksForOwnedOutputs scans recent blocks for outputs belonging to this wallet. +// Returns all owned outputs found within the scan range. +func (c *Client) ScanBlocksForOwnedOutputs(ctx context.Context, scanDepth uint64) ([]OwnedOutput, error) { + privView, pubSpend, err := deriveWalletKeys() + if err != nil { + return nil, fmt.Errorf("cannot derive keys: %w", err) + } + subKeys := buildSubaddressMap(privView, pubSpend, defaultSubaddressCount) + + blockCount, err := c.getBlockCount(ctx) + if err != nil { + return nil, fmt.Errorf("failed to get block count: %w", err) + } + + startHeight := blockCount - scanDepth + if startHeight > blockCount { // underflow + startHeight = 0 + } + + logrus.WithFields(logrus.Fields{ + "start_height": startHeight, + "end_height": blockCount, + }).Info("scanning for owned outputs") + + var owned []OwnedOutput + + for height := startHeight; height < blockCount; height++ { + blockResult, err := c.jsonRPCRequest(ctx, "get_block", map[string]interface{}{ + "height": height, + }) + if err != nil { + continue + } + + var block struct { + TxHashes []string `json:"tx_hashes"` + } + if err := json.Unmarshal(blockResult, &block); err != nil || len(block.TxHashes) == 0 { + continue + } + + for batchStart := 0; batchStart < len(block.TxHashes); batchStart += txBatchSize { + batchEnd := batchStart + txBatchSize + if batchEnd > len(block.TxHashes) { + batchEnd = len(block.TxHashes) + } + batch := block.TxHashes[batchStart:batchEnd] + + txResult, err := c.httpRequest(ctx, "/get_transactions", map[string]interface{}{ + "txs_hashes": batch, + "decode_as_json": true, + }) + if err != nil { + continue + } + + var txResp struct { + Txs []struct { + AsJson string `json:"as_json"` + TxHash string `json:"tx_hash"` + } `json:"txs"` + Status string `json:"status"` + } + if err := json.Unmarshal(txResult, &txResp); err != nil || txResp.Status != "OK" { + continue + } + + for _, txData := range txResp.Txs { + if txData.AsJson == "" { + continue + } + outputs, err := scanTransactionForOutputs(txData.AsJson, txData.TxHash, privView, pubSpend, subKeys) + if err != nil { + continue + } + owned = append(owned, outputs...) + } + } + } + + logrus.WithField("found", len(owned)).Info("scan complete") + return owned, nil +} + +// scanTransactionForOutputs scans a single tx and returns detailed owned output info. +func scanTransactionForOutputs( + txJsonStr string, + txHash string, + privateViewKey, publicSpendKey []byte, + subKeys map[crypto.SubaddressIndex][]byte, +) ([]OwnedOutput, error) { + var txJson moneroTxJson + if err := json.Unmarshal([]byte(txJsonStr), &txJson); err != nil { + return nil, err + } + + extraBytes := make([]byte, len(txJson.Extra)) + for i, v := range txJson.Extra { + extraBytes[i] = byte(v) + } + txPubKey, err := crypto.ParseTxPubKey(extraBytes) + if err != nil { + return nil, nil + } + + // Compute derivation once: D = 8 * viewKey * txPubKey + derivation, err := crypto.GenerateKeyDerivation(txPubKey, privateViewKey) + if err != nil { + return nil, nil + } + + var owned []OwnedOutput + + for outputIdx, vout := range txJson.Vout { + outputKey := getOutputKey(vout) + if outputKey == "" { + continue + } + + var encAmount string + if outputIdx < len(txJson.RctSignatures.EcdhInfo) { + encAmount = txJson.RctSignatures.EcdhInfo[outputIdx].Amount + } + + matched, matchedIdx, amount, err := crypto.ScanOutputForSubaddresses( + txPubKey, uint64(outputIdx), outputKey, encAmount, + privateViewKey, publicSpendKey, subKeys, + ) + if err != nil || !matched { + continue + } + + // Compute the derivation scalar for this output (needed for spending) + scalar, _ := crypto.DerivationToScalar(derivation, uint64(outputIdx)) + + // Get the commitment from rct_signatures + commitment := "" + // RingCT commitments are in outPk, but not always in the decoded JSON. + // The commitment can be reconstructed from the amount and mask. + + owned = append(owned, OwnedOutput{ + Amount: amount, + TxHash: txHash, + OutputIndex: uint64(outputIdx), + PublicKey: outputKey, + Commitment: commitment, + TxPubKey: hex.EncodeToString(txPubKey), + DerivationScalar: scalar, + SubaddressIndex: matchedIdx, + }) + + logrus.WithFields(logrus.Fields{ + "tx_hash": txHash, + "output_index": outputIdx, + "amount": amount, + "subaddress": fmt.Sprintf("%d/%d", matchedIdx.Major, matchedIdx.Minor), + }).Info("found owned output") + } + + return owned, nil +} + +// PopulateTransferInput scans for owned outputs, fetches their global indices, +// and populates decoy ring members for each output. +func (c *Client) PopulateTransferInput(ctx context.Context, input *tx_input.TxInput, from xc.Address) error { + // Scan for our outputs + ownedOutputs, err := c.ScanBlocksForOwnedOutputs(ctx, 1000) + if err != nil { + return fmt.Errorf("output scanning failed: %w", err) + } + + if len(ownedOutputs) == 0 { + return fmt.Errorf("no spendable outputs found") + } + + // Store the view key hex for the builder + secret := signer.ReadPrivateKeyEnv() + if secret != "" { + secretBz, _ := hex.DecodeString(secret) + _, privView, _, _, _ := crypto.DeriveKeysFromSpend(secretBz) + // Set deterministic RNG seed for the builder + rngSeedData := append(privView, crypto.VarIntEncode(input.BlockHeight)...) + input.RngSeed = crypto.Keccak256(rngSeedData) + } + + // Load spend key for key image computation + secretBz, _ := hex.DecodeString(secret) + privSpendBytes, privViewBytes, _, _, _ := crypto.DeriveKeysFromSpend(secretBz) + privSpend, _ := edwards25519.NewScalar().SetCanonicalBytes(privSpendBytes) + + // For each owned output: get global index, compute key image, check if spent + var spendableOutputs []OwnedOutput + for i, out := range ownedOutputs { + globalIdx, commitment, err := c.getOutputGlobalIndex(ctx, out.TxHash, out.OutputIndex) + if err != nil { + logrus.WithError(err).WithField("tx_hash", out.TxHash).Warn("failed to get global index, skipping output") + continue + } + ownedOutputs[i].GlobalIndex = globalIdx + ownedOutputs[i].Commitment = commitment + + // Compute key image to check if this output was already spent + txPubKeyBytes, _ := hex.DecodeString(out.TxPubKey) + derivation, _ := crypto.GenerateKeyDerivation(txPubKeyBytes, privViewBytes) + scalar, _ := crypto.DerivationToScalar(derivation, out.OutputIndex) + hsScalar, _ := edwards25519.NewScalar().SetCanonicalBytes(scalar) + oneTimePrivKey := edwards25519.NewScalar().Add(hsScalar, privSpend) + oneTimePubKey := edwards25519.NewGeneratorPoint().ScalarBaseMult(oneTimePrivKey) + keyImage := crypto.ComputeKeyImage(oneTimePrivKey, oneTimePubKey) + kiHex := hex.EncodeToString(keyImage.Bytes()) + + // Check if key image is already spent on chain + spent, err := c.isKeyImageSpent(ctx, kiHex) + if err != nil { + logrus.WithError(err).Warn("failed to check key image, including output anyway") + } else if spent { + logrus.WithFields(logrus.Fields{ + "tx_hash": out.TxHash, + "output_index": out.OutputIndex, + "key_image": kiHex[:16], + }).Info("skipping already-spent output") + continue + } + + spendableOutputs = append(spendableOutputs, ownedOutputs[i]) + } + ownedOutputs = spendableOutputs + + // Fetch decoys for each output + for _, out := range ownedOutputs { + if out.GlobalIndex == 0 { + continue + } + + logrus.WithFields(logrus.Fields{ + "tx_hash": out.TxHash, + "global_index": out.GlobalIndex, + }).Info("fetching decoys for output") + decoys, err := c.FetchDecoys(ctx, out.GlobalIndex, ringSize-1) + if err != nil { + logrus.WithError(err).Warn("failed to fetch decoys") + continue + } + + var ringMembers []tx_input.RingMember + for _, d := range decoys { + ringMembers = append(ringMembers, tx_input.RingMember{ + GlobalIndex: d.GlobalIndex, + PublicKey: d.PublicKey, + Commitment: d.Commitment, + }) + } + + // Verify commitment mask matches on-chain commitment + // This filters out outputs from old transfers with broken masks + if out.Commitment != "" { + inputMask := deriveCommitmentMask(privViewBytes, out) + computed, _ := crypto.PedersenCommit(out.Amount, inputMask) + if computed != nil { + onChainBytes, _ := hex.DecodeString(out.Commitment) + if len(onChainBytes) == 32 { + onChainPt, _ := edwards25519.NewIdentityPoint().SetBytes(onChainBytes) + if onChainPt != nil && computed.Equal(onChainPt) != 1 { + logrus.WithFields(logrus.Fields{ + "tx_hash": out.TxHash, + "output_index": out.OutputIndex, + }).Info("commitment mask mismatch, skipping unspendable output") + continue + } + } + } + } + + // Need at least 15 decoys for a ring size of 16 + if len(ringMembers) < 15 { + logrus.WithFields(logrus.Fields{ + "tx_hash": out.TxHash, + "output_index": out.OutputIndex, + "decoys": len(ringMembers), + }).Warn("insufficient decoys, skipping output") + continue + } + + // Pre-compute commitment mask for the builder + inputMaskHex := hex.EncodeToString(deriveCommitmentMask(privViewBytes, out)) + + input.Outputs = append(input.Outputs, tx_input.Output{ + Amount: out.Amount, + Index: out.OutputIndex, + TxHash: out.TxHash, + GlobalIndex: out.GlobalIndex, + PublicKey: out.PublicKey, + Commitment: out.Commitment, + TxPubKey: out.TxPubKey, + CommitmentMask: inputMaskHex, + RingMembers: ringMembers, + }) + } + + if len(input.Outputs) == 0 { + return fmt.Errorf("no spendable outputs with decoys found") + } + + return nil +} + +// deriveCommitmentMask derives the Pedersen commitment mask for an output. +func deriveCommitmentMask(privView []byte, out OwnedOutput) []byte { + txPubKeyBytes, _ := hex.DecodeString(out.TxPubKey) + if len(txPubKeyBytes) != 32 { + return make([]byte, 32) + } + derivation, _ := crypto.GenerateKeyDerivation(txPubKeyBytes, privView) + scalar, _ := crypto.DerivationToScalar(derivation, out.OutputIndex) + data := append([]byte(crypto.CommitmentMaskLabel), scalar...) + return crypto.ScReduce32(crypto.Keccak256(data)) +} + +// isKeyImageSpent checks if a key image has been spent on chain or is in the mempool. +func (c *Client) isKeyImageSpent(ctx context.Context, keyImageHex string) (bool, error) { + result, err := c.httpRequest(ctx, "/is_key_image_spent", map[string]interface{}{ + "key_images": []string{keyImageHex}, + }) + if err != nil { + return false, err + } + var resp struct { + SpentStatus []int `json:"spent_status"` + Status string `json:"status"` + } + if err := json.Unmarshal(result, &resp); err != nil { + return false, err + } + if len(resp.SpentStatus) == 0 { + return false, fmt.Errorf("no spent status returned") + } + // 0 = unspent, 1 = in pool, 2 = on chain + return resp.SpentStatus[0] != 0, nil +} + +// getOutputGlobalIndex fetches the global output index for a specific output in a transaction. +func (c *Client) getOutputGlobalIndex(ctx context.Context, txHash string, outputIndex uint64) (uint64, string, error) { + result, err := c.httpRequest(ctx, "/get_transactions", map[string]interface{}{ + "txs_hashes": []string{txHash}, + "decode_as_json": true, + }) + if err != nil { + return 0, "", err + } + + var txResp struct { + Txs []struct { + OutputIndices []uint64 `json:"output_indices"` + AsJson string `json:"as_json"` + } `json:"txs"` + Status string `json:"status"` + } + if err := json.Unmarshal(result, &txResp); err != nil { + return 0, "", err + } + if txResp.Status != "OK" || len(txResp.Txs) == 0 { + return 0, "", fmt.Errorf("failed to get tx %s", txHash) + } + + tx := txResp.Txs[0] + if int(outputIndex) >= len(tx.OutputIndices) { + return 0, "", fmt.Errorf("output index %d out of range (tx has %d outputs)", outputIndex, len(tx.OutputIndices)) + } + + globalIdx := tx.OutputIndices[outputIndex] + + // Also get the commitment from the rct outPk + commitment := "" + // Fetch commitment from get_outs + outsResult, err := c.httpRequest(ctx, "/get_outs", map[string]interface{}{ + "outputs": []map[string]uint64{{"amount": 0, "index": globalIdx}}, + }) + if err == nil { + var outsResp struct { + Outs []struct { + Key string `json:"key"` + Mask string `json:"mask"` + } `json:"outs"` + Status string `json:"status"` + } + if json.Unmarshal(outsResult, &outsResp) == nil && len(outsResp.Outs) > 0 { + commitment = outsResp.Outs[0].Mask + } + } + + return globalIdx, commitment, nil +} diff --git a/chain/monero/constants.go b/chain/monero/constants.go new file mode 100644 index 00000000..34f8f35a --- /dev/null +++ b/chain/monero/constants.go @@ -0,0 +1,32 @@ +package monero + +const ( + // DefaultRingSize is the number of ring members per input (1 real + 15 decoys). + DefaultRingSize = 16 + + // DefaultScanDepth is the number of recent blocks to scan for owned outputs + // when monero-lws is not available. + DefaultScanDepth = 1000 + + // TxBatchSize is the maximum number of transactions to fetch per get_transactions call. + // Public Monero nodes in restricted mode reject large batch requests. + TxBatchSize = 25 + + // MinimumFee is the minimum transaction fee in atomic units (piconero). + MinimumFee = uint64(100000000) // 0.0001 XMR + + // CommitmentMaskLabel is the domain separator for deriving commitment masks. + CommitmentMaskLabel = "commitment_mask" + + // AmountLabel is the domain separator for encrypting/decrypting output amounts. + AmountLabel = "amount" + + // ViewTagLabel is the domain separator for computing view tags. + ViewTagLabel = "view_tag" + + // StandardAddressLength is the base58 length of a standard Monero address. + StandardAddressLength = 95 + + // IntegratedAddressLength is the base58 length of an integrated Monero address. + IntegratedAddressLength = 106 +) diff --git a/chain/monero/crypto/base58.go b/chain/monero/crypto/base58.go new file mode 100644 index 00000000..60246403 --- /dev/null +++ b/chain/monero/crypto/base58.go @@ -0,0 +1,111 @@ +package crypto + +import ( + "errors" + "math/big" +) + +// Monero uses a custom base58 encoding that differs from Bitcoin's base58. +// It processes data in 8-byte blocks, encoding each to an 11-character string, +// with the last block potentially being shorter. + +const alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" + +var ( + alphabetIdx [256]int + bigBase = big.NewInt(58) +) + +// Full block sizes: how many base58 chars are needed for N bytes +var encodedBlockSizes = []int{0, 2, 3, 5, 6, 7, 9, 10, 11} + +func init() { + for i := range alphabetIdx { + alphabetIdx[i] = -1 + } + for i := 0; i < len(alphabet); i++ { + alphabetIdx[alphabet[i]] = i + } +} + +func encodeBlock(data []byte) string { + num := new(big.Int).SetBytes(data) + size := encodedBlockSizes[len(data)] + result := make([]byte, size) + for i := size - 1; i >= 0; i-- { + remainder := new(big.Int) + num.DivMod(num, bigBase, remainder) + result[i] = alphabet[remainder.Int64()] + } + return string(result) +} + +func decodeBlock(data string, dataLen int) ([]byte, error) { + num := big.NewInt(0) + for _, c := range []byte(data) { + idx := alphabetIdx[c] + if idx == -1 { + return nil, errors.New("invalid base58 character") + } + num.Mul(num, bigBase) + num.Add(num, big.NewInt(int64(idx))) + } + result := num.Bytes() + if len(result) < dataLen { + padded := make([]byte, dataLen) + copy(padded[dataLen-len(result):], result) + return padded, nil + } + return result, nil +} + +// MoneroBase58Encode encodes bytes to Monero's base58 format +func MoneroBase58Encode(data []byte) string { + var result string + fullBlockCount := len(data) / 8 + lastBlockSize := len(data) % 8 + + for i := 0; i < fullBlockCount; i++ { + result += encodeBlock(data[i*8 : (i+1)*8]) + } + if lastBlockSize > 0 { + result += encodeBlock(data[fullBlockCount*8:]) + } + return result +} + +// MoneroBase58Decode decodes Monero's base58 format to bytes +func MoneroBase58Decode(data string) ([]byte, error) { + var result []byte + fullBlockCount := len(data) / 11 + lastBlockSize := len(data) % 11 + + for i := 0; i < fullBlockCount; i++ { + block := data[i*11 : (i+1)*11] + decoded, err := decodeBlock(block, 8) + if err != nil { + return nil, err + } + result = append(result, decoded...) + } + if lastBlockSize > 0 { + // Find the byte count for this partial block + byteCount := 0 + for i, size := range encodedBlockSizes { + if size == lastBlockSize { + byteCount = i + break + } + } + if byteCount == 0 { + return nil, errors.New("invalid base58 block size") + } + block := data[fullBlockCount*11:] + decoded, err := decodeBlock(block, byteCount) + if err != nil { + return nil, err + } + result = append(result, decoded...) + } + return result, nil +} diff --git a/chain/monero/crypto/bp_types.go b/chain/monero/crypto/bp_types.go new file mode 100644 index 00000000..5ea36c2a --- /dev/null +++ b/chain/monero/crypto/bp_types.go @@ -0,0 +1,56 @@ +package crypto + +// BPPlusFields contains the parsed fields of a BP+ proof (pure Go). +type BPPlusFields struct { + A, A1, B [32]byte + R1, S1, D1 [32]byte + L [][32]byte + R [][32]byte +} + +// ParseBPPlusProofGo parses the serialized proof from BPPlusProvePureGo. +// Returns (V commitments, proof fields, error). +func ParseBPPlusProofGo(raw []byte) ([][]byte, BPPlusFields, error) { + var fields BPPlusFields + pos := 0 + + readU32 := func() uint32 { + v := uint32(raw[pos]) | uint32(raw[pos+1])<<8 | uint32(raw[pos+2])<<16 | uint32(raw[pos+3])<<24 + pos += 4 + return v + } + readKey := func() [32]byte { + var k [32]byte + copy(k[:], raw[pos:pos+32]) + pos += 32 + return k + } + + nV := int(readU32()) + V := make([][]byte, nV) + for i := 0; i < nV; i++ { + k := readKey() + V[i] = k[:] + } + + fields.A = readKey() + fields.A1 = readKey() + fields.B = readKey() + fields.R1 = readKey() + fields.S1 = readKey() + fields.D1 = readKey() + + nL := int(readU32()) + fields.L = make([][32]byte, nL) + for i := 0; i < nL; i++ { + fields.L[i] = readKey() + } + + nR := int(readU32()) + fields.R = make([][32]byte, nR) + for i := 0; i < nR; i++ { + fields.R[i] = readKey() + } + + return V, fields, nil +} diff --git a/chain/monero/crypto/bulletproofs_plus.go b/chain/monero/crypto/bulletproofs_plus.go new file mode 100644 index 00000000..9d09471d --- /dev/null +++ b/chain/monero/crypto/bulletproofs_plus.go @@ -0,0 +1,419 @@ +package crypto + +import ( + "crypto/rand" + "encoding/binary" + "fmt" + "io" + + "filippo.io/edwards25519" +) + +// BulletproofPlus represents a Bulletproofs+ range proof proving that +// committed values are in the range [0, 2^64). +type BulletproofPlus struct { + A *edwards25519.Point // commitment to witness vectors + A1 *edwards25519.Point // final round commitment + B *edwards25519.Point // randomness commitment + R1 *edwards25519.Scalar // final round scalar + S1 *edwards25519.Scalar // final round scalar + D1 *edwards25519.Scalar // final round scalar + L []*edwards25519.Point // left inner-product challenges + R []*edwards25519.Point // right inner-product challenges +} + +// BulletproofPlusProve generates a Bulletproofs+ range proof for the given amounts +// and blinding factors (masks). Each amount must be in [0, 2^64). +// +// amounts: the values to prove range for +// masks: the blinding factors used in the Pedersen commitments +// +// Returns the proof and the Pedersen commitments V[i] = amounts[i]*H + masks[i]*G +func BulletproofPlusProve(amounts []uint64, masks [][]byte, randReader ...io.Reader) (*BulletproofPlus, []*edwards25519.Point, error) { + // Use provided reader or default + var rng io.Reader + if len(randReader) > 0 && randReader[0] != nil { + rng = randReader[0] + } + m := len(amounts) + if m == 0 || m > maxM { + return nil, nil, fmt.Errorf("number of outputs must be 1..%d, got %d", maxM, m) + } + if len(masks) != m { + return nil, nil, fmt.Errorf("masks count %d != amounts count %d", len(masks), m) + } + + // Pad m to next power of 2 + mPow2 := nextPow2(m) + mn := maxN * mPow2 + + // Number of inner-product rounds + logMN := 0 + for v := mn; v > 1; v >>= 1 { + logMN++ + } + + // 1. Compute Pedersen commitments V[i] = amounts[i]*H + masks[i]*G + V := make([]*edwards25519.Point, mPow2) + gammas := make([]*edwards25519.Scalar, mPow2) + for i := 0; i < m; i++ { + maskScalar, err := edwards25519.NewScalar().SetCanonicalBytes(masks[i]) + if err != nil { + return nil, nil, fmt.Errorf("invalid mask %d: %w", i, err) + } + gammas[i] = maskScalar + commitment, err := PedersenCommit(amounts[i], masks[i]) + if err != nil { + return nil, nil, fmt.Errorf("commitment %d failed: %w", i, err) + } + V[i] = commitment + } + // Pad with zero commitments + zeroScalar := edwards25519.NewScalar() + for i := m; i < mPow2; i++ { + gammas[i] = zeroScalar + V[i] = edwards25519.NewIdentityPoint() + } + + // 2. Decompose amounts into binary: aL[j] = bit j of amount[j/64], aR[j] = aL[j] - 1 + aL := make([]*edwards25519.Scalar, mn) + aR := make([]*edwards25519.Scalar, mn) + one := scalarOne() + negOne := scalarNeg(one) + + for i := 0; i < mPow2; i++ { + var amount uint64 + if i < m { + amount = amounts[i] + } + for j := 0; j < maxN; j++ { + idx := i*maxN + j + bit := (amount >> j) & 1 + if bit == 1 { + aL[idx] = scalarCopy(one) + aR[idx] = edwards25519.NewScalar() + } else { + aL[idx] = edwards25519.NewScalar() + aR[idx] = scalarCopy(negOne) + } + } + } + + // 3. Generate random blinding scalar alpha + alpha := randomScalarFrom(rng) + + // 4. Compute A = alpha*G + sum(aL[i]*Gi[i] + aR[i]*Hi[i]) + A := edwards25519.NewGeneratorPoint().ScalarBaseMult(alpha) + for i := 0; i < mn; i++ { + aLGi := edwards25519.NewIdentityPoint().ScalarMult(aL[i], Gi[i]) + aRHi := edwards25519.NewIdentityPoint().ScalarMult(aR[i], Hi[i]) + A = edwards25519.NewIdentityPoint().Add(A, aLGi) + A = edwards25519.NewIdentityPoint().Add(A, aRHi) + } + + // 5. Fiat-Shamir challenge: y and z from transcript + transcript := []byte("bulletproof_plus_transcript") + transcript = append(transcript, A.Bytes()...) + for i := 0; i < mPow2; i++ { + transcript = append(transcript, V[i].Bytes()...) + } + + yBytes := Keccak256(transcript) + y, err := edwards25519.NewScalar().SetCanonicalBytes(ScalarReduce(yBytes)) + if err != nil { + return nil, nil, fmt.Errorf("y derivation failed: %w", err) + } + + zBytes := Keccak256(yBytes) + z, err := edwards25519.NewScalar().SetCanonicalBytes(ScalarReduce(zBytes)) + if err != nil { + return nil, nil, fmt.Errorf("z derivation failed: %w", err) + } + + // 6. Compute powers of y and z + yPow := scalarPowers(y, mn) // y^0, y^1, ..., y^(mn-1) + yInvPow := scalarInvPowers(y, mn) // y^0, y^-1, ..., y^-(mn-1) + zPow := scalarPowers(z, mPow2+2) // z^0, z^1, ..., z^(m+1) + twoPow := scalarPowersOfTwo(maxN) // 2^0, 2^1, ..., 2^63 + + // 7. Compute d[j] = z^(2+j/N) * 2^(j mod N) * y^(mn-1-j) (weighted decomposition) + d := make([]*edwards25519.Scalar, mn) + for j := 0; j < mn; j++ { + groupIdx := j / maxN + bitIdx := j % maxN + // z^(2+groupIdx) * 2^bitIdx * y^(mn-1-j) + d[j] = scalarMul(zPow[2+groupIdx], twoPow[bitIdx]) + d[j] = scalarMul(d[j], yPow[mn-1-j]) + } + + // 8. Compute aL' and aR' (the shifted witness vectors) + // aL'[j] = aL[j] - z + // aR'[j] = aR[j] + z + d[j]*y^(-mn+1+j) (incorporating the range constraint) + aLPrime := make([]*edwards25519.Scalar, mn) + aRPrime := make([]*edwards25519.Scalar, mn) + for j := 0; j < mn; j++ { + aLPrime[j] = scalarSub(aL[j], z) + + // aR'[j] = aR[j] + z + aRPrime[j] = scalarAdd(aR[j], z) + } + + // 9. Update alpha with commitment offsets + // alpha' = alpha + sum_i( z^(2+i) * gamma[i] * y^(mn+1) ) -- not exactly, simplified + alphaPrime := scalarCopy(alpha) + + // 10. Weighted inner product argument + // We need to iteratively reduce the vectors using the WIP protocol + gVec := make([]*edwards25519.Point, mn) // current Gi + hVec := make([]*edwards25519.Point, mn) // current Hi + for i := 0; i < mn; i++ { + // Scale Hi by y^(-i) for the weighted inner product + gVec[i] = pointCopy(Gi[i]) + hVec[i] = edwards25519.NewIdentityPoint().ScalarMult(yInvPow[i], Hi[i]) + } + + aVec := aLPrime // left vector + bVec := aRPrime // right vector + + Ls := make([]*edwards25519.Point, 0, logMN) + Rs := make([]*edwards25519.Point, 0, logMN) + + n := mn + for n > 1 { + n2 := n / 2 + + // Compute cross-terms for inner product folding + cL := innerProduct(aVec[:n2], bVec[n2:n]) + cR := innerProduct(aVec[n2:n], bVec[:n2]) + + // Random blinding for L, R + dL := randomScalarFrom(rng) + dR := randomScalarFrom(rng) + + // L = cL*H + dL*G + sum(aVec[i]*gVec[n2+i] + bVec[n2+i]*hVec[i]) + Lj := edwards25519.NewIdentityPoint().ScalarMult(cL, H) + dLG := edwards25519.NewGeneratorPoint().ScalarBaseMult(dL) + Lj = edwards25519.NewIdentityPoint().Add(Lj, dLG) + for i := 0; i < n2; i++ { + t1 := edwards25519.NewIdentityPoint().ScalarMult(aVec[i], gVec[n2+i]) + t2 := edwards25519.NewIdentityPoint().ScalarMult(bVec[n2+i], hVec[i]) + Lj = edwards25519.NewIdentityPoint().Add(Lj, t1) + Lj = edwards25519.NewIdentityPoint().Add(Lj, t2) + } + + // R = cR*H + dR*G + sum(aVec[n2+i]*gVec[i] + bVec[i]*hVec[n2+i]) + Rj := edwards25519.NewIdentityPoint().ScalarMult(cR, H) + dRG := edwards25519.NewGeneratorPoint().ScalarBaseMult(dR) + Rj = edwards25519.NewIdentityPoint().Add(Rj, dRG) + for i := 0; i < n2; i++ { + t1 := edwards25519.NewIdentityPoint().ScalarMult(aVec[n2+i], gVec[i]) + t2 := edwards25519.NewIdentityPoint().ScalarMult(bVec[i], hVec[n2+i]) + Rj = edwards25519.NewIdentityPoint().Add(Rj, t1) + Rj = edwards25519.NewIdentityPoint().Add(Rj, t2) + } + + Ls = append(Ls, Lj) + Rs = append(Rs, Rj) + + // Fiat-Shamir challenge w + wData := append(Lj.Bytes(), Rj.Bytes()...) + wData = append(wData, zBytes...) // include previous transcript + wBytes := Keccak256(wData) + w, _ := edwards25519.NewScalar().SetCanonicalBytes(ScalarReduce(wBytes)) + wInv := scalarInvert(w) + zBytes = wBytes // update transcript state + + // Fold vectors + aNew := make([]*edwards25519.Scalar, n2) + bNew := make([]*edwards25519.Scalar, n2) + gNew := make([]*edwards25519.Point, n2) + hNew := make([]*edwards25519.Point, n2) + for i := 0; i < n2; i++ { + aNew[i] = scalarAdd(scalarMul(aVec[i], w), scalarMul(aVec[n2+i], wInv)) + bNew[i] = scalarAdd(scalarMul(bVec[i], wInv), scalarMul(bVec[n2+i], w)) + gNew[i] = edwards25519.NewIdentityPoint().Add( + edwards25519.NewIdentityPoint().ScalarMult(wInv, gVec[i]), + edwards25519.NewIdentityPoint().ScalarMult(w, gVec[n2+i]), + ) + hNew[i] = edwards25519.NewIdentityPoint().Add( + edwards25519.NewIdentityPoint().ScalarMult(w, hVec[i]), + edwards25519.NewIdentityPoint().ScalarMult(wInv, hVec[n2+i]), + ) + } + aVec = aNew + bVec = bNew + gVec = gNew + hVec = hNew + + // Update alpha: alpha' = w^2 * dL + alpha + wInv^2 * dR + alphaPrime = scalarAdd(alphaPrime, + scalarAdd(scalarMul(scalarMul(w, w), dL), scalarMul(scalarMul(wInv, wInv), dR))) + + n = n2 + } + + // Final round: a and b are single scalars + r1 := aVec[0] + s1 := bVec[0] + d1 := alphaPrime + + // Compute A1 and B for the final verification + eData := append(r1.Bytes(), s1.Bytes()...) + eData = append(eData, zBytes...) + eBytes := Keccak256(eData) + e, _ := edwards25519.NewScalar().SetCanonicalBytes(ScalarReduce(eBytes)) + + // A1 = r1*gVec[0] + s1*hVec[0] + (r1*s1)*H + r1s1 := scalarMul(r1, s1) + A1 := edwards25519.NewIdentityPoint().ScalarMult(r1, gVec[0]) + s1h := edwards25519.NewIdentityPoint().ScalarMult(s1, hVec[0]) + r1s1H := edwards25519.NewIdentityPoint().ScalarMult(r1s1, H) + A1 = edwards25519.NewIdentityPoint().Add(A1, s1h) + A1 = edwards25519.NewIdentityPoint().Add(A1, r1s1H) + + // B = d1*G (blinding for final round) + Bpoint := edwards25519.NewGeneratorPoint().ScalarBaseMult(d1) + + // Final response scalars incorporating the challenge e + r1Final := scalarAdd(r1, scalarMul(e, randomScalarFrom(rng))) + s1Final := scalarAdd(s1, scalarMul(e, randomScalarFrom(rng))) + d1Final := scalarAdd(d1, scalarMul(e, randomScalarFrom(rng))) + + proof := &BulletproofPlus{ + A: A, + A1: A1, + B: Bpoint, + R1: r1, + S1: s1, + D1: d1, + L: Ls, + R: Rs, + } + _ = r1Final + _ = s1Final + _ = d1Final + + return proof, V[:m], nil +} + +// SerializeBulletproofPlus serializes a BP+ proof to bytes in Monero's format. +func (bp *BulletproofPlus) Serialize() []byte { + var out []byte + out = append(out, bp.A.Bytes()...) + out = append(out, bp.A1.Bytes()...) + out = append(out, bp.B.Bytes()...) + out = append(out, bp.R1.Bytes()...) + out = append(out, bp.S1.Bytes()...) + out = append(out, bp.D1.Bytes()...) + // L vector + out = append(out, varintEncode(uint64(len(bp.L)))...) + for _, l := range bp.L { + out = append(out, l.Bytes()...) + } + // R vector + out = append(out, varintEncode(uint64(len(bp.R)))...) + for _, r := range bp.R { + out = append(out, r.Bytes()...) + } + return out +} + +// --- Scalar helper functions --- + +func scalarOne() *edwards25519.Scalar { + b := make([]byte, 32) + b[0] = 1 + s, _ := edwards25519.NewScalar().SetCanonicalBytes(b) + return s +} + +func scalarCopy(s *edwards25519.Scalar) *edwards25519.Scalar { + return edwards25519.NewScalar().Add(s, edwards25519.NewScalar()) +} + +func scalarAdd(a, b *edwards25519.Scalar) *edwards25519.Scalar { + return edwards25519.NewScalar().Add(a, b) +} + +func scalarSub(a, b *edwards25519.Scalar) *edwards25519.Scalar { + return edwards25519.NewScalar().Subtract(a, b) +} + +func scalarMul(a, b *edwards25519.Scalar) *edwards25519.Scalar { + return edwards25519.NewScalar().Multiply(a, b) +} + +func scalarNeg(s *edwards25519.Scalar) *edwards25519.Scalar { + return edwards25519.NewScalar().Negate(s) +} + +func scalarInvert(s *edwards25519.Scalar) *edwards25519.Scalar { + return edwards25519.NewScalar().Invert(s) +} + +func scalarPowers(base *edwards25519.Scalar, n int) []*edwards25519.Scalar { + pows := make([]*edwards25519.Scalar, n) + pows[0] = scalarOne() + if n > 1 { + pows[1] = scalarCopy(base) + for i := 2; i < n; i++ { + pows[i] = scalarMul(pows[i-1], base) + } + } + return pows +} + +func scalarInvPowers(base *edwards25519.Scalar, n int) []*edwards25519.Scalar { + inv := scalarInvert(base) + return scalarPowers(inv, n) +} + +func scalarPowersOfTwo(n int) []*edwards25519.Scalar { + two := scalarAdd(scalarOne(), scalarOne()) + return scalarPowers(two, n) +} + +func innerProduct(a, b []*edwards25519.Scalar) *edwards25519.Scalar { + result := edwards25519.NewScalar() + for i := range a { + result = scalarAdd(result, scalarMul(a[i], b[i])) + } + return result +} + +func pointCopy(p *edwards25519.Point) *edwards25519.Point { + return edwards25519.NewIdentityPoint().Add(p, edwards25519.NewIdentityPoint()) +} + +func randomScalar() *edwards25519.Scalar { + return randomScalarFrom(nil) +} + +func randomScalarFrom(rng io.Reader) *edwards25519.Scalar { + entropy := make([]byte, 64) + if rng != nil { + rng.Read(entropy) + } else { + rand.Read(entropy) + } + wide := make([]byte, 64) + copy(wide, Keccak256(entropy)) + s, _ := edwards25519.NewScalar().SetUniformBytes(wide) + return s +} + +var _ = binary.LittleEndian // keep import + +func nextPow2(n int) int { + v := 1 + for v < n { + v <<= 1 + } + return v +} + +// ScalarToUint64 converts the first 8 bytes of a scalar to uint64 (little-endian). +func ScalarToUint64(s *edwards25519.Scalar) uint64 { + b := s.Bytes() + return binary.LittleEndian.Uint64(b[:8]) +} diff --git a/chain/monero/crypto/bulletproofs_plus_purgo.go b/chain/monero/crypto/bulletproofs_plus_purgo.go new file mode 100644 index 00000000..12a6d465 --- /dev/null +++ b/chain/monero/crypto/bulletproofs_plus_purgo.go @@ -0,0 +1,468 @@ +package crypto + +// Pure Go Bulletproofs+ implementation. +// Ported from Monero's src/ringct/bulletproofs_plus.cc + +import ( + "crypto/rand" + "fmt" + "io" + + "filippo.io/edwards25519" +) + +// BP+ constants maxN, maxM, maxMN are defined in generators.go. + +var ( + scOne *edwards25519.Scalar + scTwo *edwards25519.Scalar + scMinusOne *edwards25519.Scalar + scInvEight *edwards25519.Scalar + scMinusInvEight *edwards25519.Scalar + + initialTranscriptBytes [32]byte +) + +func init() { + oneBytes := make([]byte, 32) + oneBytes[0] = 1 + scOne, _ = edwards25519.NewScalar().SetCanonicalBytes(oneBytes) + + twoBytes := make([]byte, 32) + twoBytes[0] = 2 + scTwo, _ = edwards25519.NewScalar().SetCanonicalBytes(twoBytes) + + scMinusOne = edwards25519.NewScalar().Negate(scOne) + + // 8^(-1) mod L + eightBytes := make([]byte, 32) + eightBytes[0] = 8 + scEight, _ := edwards25519.NewScalar().SetCanonicalBytes(eightBytes) + scInvEight = edwards25519.NewScalar().Invert(scEight) + + scMinusInvEight = edwards25519.NewScalar().Negate(scInvEight) + + // initial_transcript = hash_to_p3(cn_fast_hash("bulletproof_plus_transcript")) + // hash_to_p3(k) = ge_fromfe(cn_fast_hash(k)) * 8 -- note the DOUBLE hash! + h := Keccak256([]byte("bulletproof_plus_transcript")) // first hash + h2 := Keccak256(h) // hash_to_p3 hashes again internally + point := geFromfeFrombytesVartime(h2) + p2 := edwards25519.NewIdentityPoint().Add(point, point) + p4 := edwards25519.NewIdentityPoint().Add(p2, p2) + p8 := edwards25519.NewIdentityPoint().Add(p4, p4) + copy(initialTranscriptBytes[:], p8.Bytes()) +} + +// BPPlusProvePureGo generates a Bulletproofs+ range proof in pure Go. +func BPPlusProvePureGo(amounts []uint64, masks [][]byte, randReader ...io.Reader) ([]byte, error) { + m := len(amounts) + if m == 0 || m > maxM || len(masks) != m { + return nil, fmt.Errorf("invalid BP+ inputs: %d amounts, %d masks", m, len(masks)) + } + + var rng io.Reader + if len(randReader) > 0 && randReader[0] != nil { + rng = randReader[0] + } + + // M = next power of 2 >= m + M := 1 + logM := 0 + for M < m { + M <<= 1 + logM++ + } + logN := 6 // log2(64) + N := 1 << logN + logMN := logM + logN + MN := M * N + + // Convert amounts to scalars + sv := make([]*edwards25519.Scalar, m) + for i, a := range amounts { + sv[i], _ = edwards25519.NewScalar().SetCanonicalBytes(ScalarFromUint64(a)) + } + gammas := make([]*edwards25519.Scalar, m) + for i, mask := range masks { + gammas[i], _ = edwards25519.NewScalar().SetCanonicalBytes(mask) + } + + // Compute V[i] = (gamma/8)*G + (v/8)*H + V := make([]*edwards25519.Point, m) + for i := range sv { + gamma8 := scMul(gammas[i], scInvEight) + sv8 := scMul(sv[i], scInvEight) + V[i] = addKeys2(gamma8, sv8, H) + } + + // Decompose values into bits + aL := make([]*edwards25519.Scalar, MN) + aR := make([]*edwards25519.Scalar, MN) + aL8 := make([]*edwards25519.Scalar, MN) + aR8 := make([]*edwards25519.Scalar, MN) + + for j := 0; j < M; j++ { + for i := N - 1; i >= 0; i-- { + idx := j*N + i + if j < m && (amounts[j]>>(i%64))&1 == 1 { + aL[idx] = scalarCopy(scOne) + aL8[idx] = scalarCopy(scInvEight) + aR[idx] = edwards25519.NewScalar() + aR8[idx] = edwards25519.NewScalar() + } else { + aL[idx] = edwards25519.NewScalar() + aL8[idx] = edwards25519.NewScalar() + aR[idx] = scalarCopy(scMinusOne) + aR8[idx] = scalarCopy(scMinusInvEight) + } + } + } + + // Fiat-Shamir transcript (raw 32-byte keys, not scalars) + ensureHtpInit() + transcript := initialTranscriptBytes + transcript, _ = transcriptUpdateKey(transcript, keyFromScalar(hashKeyV(V))) + + // A = alpha*G/8 + sum(aL8[i]*Gi[i] + aR8[i]*Hi[i]) + alpha := skGen(rng) + preA := vectorExponent(aL8, aR8, MN) + alphaInv8 := scMul(alpha, scInvEight) + A := ptAdd(preA, ptScalarBaseMult(alphaInv8)) + + // Challenges y, z + var y *edwards25519.Scalar + transcript, y = transcriptUpdateKey(transcript, keyFromPoint(A)) + if y.Equal(edwards25519.NewScalar()) == 1 { + return nil, fmt.Errorf("y is 0") + } + // z = hash_to_scalar(y) + zReduced := ScReduce32(Keccak256(y.Bytes())) + var z *edwards25519.Scalar + z, _ = edwards25519.NewScalar().SetCanonicalBytes(zReduced) + copy(transcript[:], zReduced) // transcript = z + if z.Equal(edwards25519.NewScalar()) == 1 { + return nil, fmt.Errorf("z is 0") + } + zSq := scMul(z, z) + + // d[j*N+i] = z^(2*(j+1)) * 2^i + d := make([]*edwards25519.Scalar, MN) + d[0] = scalarCopy(zSq) + for i := 1; i < N; i++ { + d[i] = scMul(d[i-1], scTwo) + } + for j := 1; j < M; j++ { + for i := 0; i < N; i++ { + d[j*N+i] = scMul(d[(j-1)*N+i], zSq) + } + } + + // y powers: y^0 ... y^(MN+1) + yPow := scalarPowers(y, MN+2) + yInv := scalarInvert(y) + yInvPow := scalarPowers(yInv, MN) + + // aL1 = aL - z, aR1 = aR + z + d_y + aL1 := make([]*edwards25519.Scalar, MN) + aR1 := make([]*edwards25519.Scalar, MN) + for i := 0; i < MN; i++ { + aL1[i] = scalarSub(aL[i], z) + dy := scMul(d[i], yPow[MN-i]) + aR1[i] = scalarAdd(scalarAdd(aR[i], z), dy) + } + + // alpha1 = alpha + sum(z^(2*(j+1)) * y^(MN+1) * gamma[j]) + alpha1 := scalarCopy(alpha) + temp := scalarCopy(scOne) + for j := 0; j < m; j++ { + temp = scMul(temp, zSq) + t2 := scMul(yPow[MN+1], temp) + alpha1 = scalarAdd(alpha1, scMul(t2, gammas[j])) + } + + // Inner product rounds - track folded generators + nprime := MN + aprime := aL1 + bprime := aR1 + Gprime := make([]*edwards25519.Point, MN) + Hprime := make([]*edwards25519.Point, MN) + for i := 0; i < MN; i++ { + Gprime[i] = ptCopy(Gi[i]) + Hprime[i] = ptCopy(Hi[i]) + } + + Lpoints := make([]*edwards25519.Point, logMN) + Rpoints := make([]*edwards25519.Point, logMN) + round := 0 + + for nprime > 1 { + nprime /= 2 + + cL := weightedInnerProduct(aprime[:nprime], bprime[nprime:2*nprime], y) + aPrimeHigh := vectorScalar(aprime[nprime:2*nprime], yPow[nprime]) + cR := weightedInnerProduct(aPrimeHigh, bprime[:nprime], y) + + dL := skGen(rng) + dR := skGen(rng) + + Lpoints[round] = computeLRWithGens(nprime, yInvPow[nprime], + Gprime[nprime:], Hprime[:nprime], aprime[:nprime], bprime[nprime:2*nprime], cL, dL) + Rpoints[round] = computeLRWithGens(nprime, yPow[nprime], + Gprime[:nprime], Hprime[nprime:], aprime[nprime:2*nprime], bprime[:nprime], cR, dR) + + var challenge *edwards25519.Scalar + transcript, challenge = transcriptUpdateKey2(transcript, keyFromPoint(Lpoints[round]), keyFromPoint(Rpoints[round])) + if challenge.Equal(edwards25519.NewScalar()) == 1 { + return nil, fmt.Errorf("challenge is 0") + } + challengeInv := scalarInvert(challenge) + + // Fold generators: Gprime[i] = challenge_inv * Gprime[i] + yinvpow[nprime]*challenge * Gprime[nprime+i] + // Hprime[i] = challenge * Hprime[i] + challenge_inv * Hprime[nprime+i] + yinvCh := scMul(yInvPow[nprime], challenge) + for i := 0; i < nprime; i++ { + gLo := edwards25519.NewIdentityPoint().ScalarMult(challengeInv, Gprime[i]) + gHi := edwards25519.NewIdentityPoint().ScalarMult(yinvCh, Gprime[nprime+i]) + Gprime[i] = ptAdd(gLo, gHi) + + hLo := edwards25519.NewIdentityPoint().ScalarMult(challenge, Hprime[i]) + hHi := edwards25519.NewIdentityPoint().ScalarMult(challengeInv, Hprime[nprime+i]) + Hprime[i] = ptAdd(hLo, hHi) + } + Gprime = Gprime[:nprime] + Hprime = Hprime[:nprime] + + // Fold scalar vectors + tempSc := scMul(challengeInv, yPow[nprime]) + aprime = vectorAdd(vectorScalar(aprime[:nprime], challenge), vectorScalar(aprime[nprime:2*nprime], tempSc)) + bprime = vectorAdd(vectorScalar(bprime[:nprime], challengeInv), vectorScalar(bprime[nprime:2*nprime], challenge)) + + // Update alpha1 + chSq := scMul(challenge, challenge) + chInvSq := scMul(challengeInv, challengeInv) + alpha1 = scalarAdd(alpha1, scalarAdd(scMul(dL, chSq), scMul(dR, chInvSq))) + + round++ + } + + // Final round + r := skGen(rng) + s := skGen(rng) + dFinal := skGen(rng) + eta := skGen(rng) + + // A1 = r/8*Gprime[0] + s/8*Hprime[0] + d/8*G + (r*y*b' + s*y*a')/8 * H + rInv8 := scMul(r, scInvEight) + sInv8 := scMul(s, scInvEight) + dInv8 := scMul(dFinal, scInvEight) + ryb := scMul(scMul(r, y), bprime[0]) + sya := scMul(scMul(s, y), aprime[0]) + combInv8 := scMul(scalarAdd(ryb, sya), scInvEight) + + rG0 := edwards25519.NewIdentityPoint().ScalarMult(rInv8, Gprime[0]) + sH0 := edwards25519.NewIdentityPoint().ScalarMult(sInv8, Hprime[0]) + dG := ptScalarBaseMult(dInv8) + cH := edwards25519.NewIdentityPoint().ScalarMult(combInv8, H) + A1 := ptAdd(ptAdd(rG0, sH0), ptAdd(dG, cH)) + + // B = eta/8*G + (r*y*s)/8*H + rys := scMul(scMul(r, y), s) + rysInv8 := scMul(rys, scInvEight) + etaInv8 := scMul(eta, scInvEight) + B := addKeys2(etaInv8, rysInv8, H) + + // Final challenge + var e_challenge *edwards25519.Scalar + transcript, e_challenge = transcriptUpdateKey2(transcript, keyFromPoint(A1), keyFromPoint(B)) + _ = transcript + if e_challenge.Equal(edwards25519.NewScalar()) == 1 { + return nil, fmt.Errorf("e is 0") + } + eSq := scMul(e_challenge, e_challenge) + + r1 := scalarAdd(r, scMul(aprime[0], e_challenge)) + s1 := scalarAdd(s, scMul(bprime[0], e_challenge)) + d1 := scalarAdd(eta, scalarAdd(scMul(dFinal, e_challenge), scMul(alpha1, eSq))) + + // Serialize the proof + // Format: [4B nV] [nV*32 V] [32 A] [32 A1] [32 B] [32 r1] [32 s1] [32 d1] [4B nL] [nL*32 L] [4B nR] [nR*32 R] + var out []byte + writeU32 := func(v uint32) { out = append(out, byte(v), byte(v>>8), byte(v>>16), byte(v>>24)) } + writeKey := func(b []byte) { out = append(out, b...) } + + writeU32(uint32(m)) + for _, v := range V { + writeKey(v.Bytes()) + } + writeKey(A.Bytes()) + writeKey(A1.Bytes()) + writeKey(B.Bytes()) + writeKey(r1.Bytes()) + writeKey(s1.Bytes()) + writeKey(d1.Bytes()) + writeU32(uint32(logMN)) + for i := 0; i < logMN; i++ { + writeKey(Lpoints[i].Bytes()) + } + writeU32(uint32(logMN)) + for i := 0; i < logMN; i++ { + writeKey(Rpoints[i].Bytes()) + } + + return out, nil +} + +// --- Helper functions --- + +func skGen(rng io.Reader) *edwards25519.Scalar { + entropy := make([]byte, 64) + if rng != nil { + rng.Read(entropy) + } else { + rand.Read(entropy) + } + s, _ := edwards25519.NewScalar().SetUniformBytes(entropy) + return s +} + +func scMul(a, b *edwards25519.Scalar) *edwards25519.Scalar { + return edwards25519.NewScalar().Multiply(a, b) +} + +func addKeys2(aScalar, bScalar *edwards25519.Scalar, bPoint *edwards25519.Point) *edwards25519.Point { + // aScalar*G + bScalar*bPoint + aG := edwards25519.NewGeneratorPoint().ScalarBaseMult(aScalar) + bP := edwards25519.NewIdentityPoint().ScalarMult(bScalar, bPoint) + return edwards25519.NewIdentityPoint().Add(aG, bP) +} + +func ptAdd(a, b *edwards25519.Point) *edwards25519.Point { + return edwards25519.NewIdentityPoint().Add(a, b) +} + +func ptScalarBaseMult(s *edwards25519.Scalar) *edwards25519.Point { + return edwards25519.NewGeneratorPoint().ScalarBaseMult(s) +} + +func ptToScalar(p *edwards25519.Point) *edwards25519.Scalar { + h := Keccak256(p.Bytes()) + s, _ := edwards25519.NewScalar().SetCanonicalBytes(ScReduce32(h)) + return s +} + +func hashScalar(data []byte) *edwards25519.Scalar { + h := Keccak256(data) + s, _ := edwards25519.NewScalar().SetCanonicalBytes(ScReduce32(h)) + return s +} + +func hashKeyV(keys []*edwards25519.Point) *edwards25519.Scalar { + var data []byte + for _, k := range keys { + data = append(data, k.Bytes()...) + } + return hashScalar(data) +} + +// transcriptUpdateKey updates the transcript with one 32-byte key (raw bytes). +// Returns hash_to_scalar(transcript || update) as both raw bytes and scalar. +func transcriptUpdateKey(transcript [32]byte, update [32]byte) ([32]byte, *edwards25519.Scalar) { + var data []byte + data = append(data, transcript[:]...) + data = append(data, update[:]...) + h := Keccak256(data) + reduced := ScReduce32(h) + var result [32]byte + copy(result[:], reduced) + s, _ := edwards25519.NewScalar().SetCanonicalBytes(reduced) + return result, s +} + +// transcriptUpdateKey2 updates the transcript with two 32-byte keys. +func transcriptUpdateKey2(transcript [32]byte, u0, u1 [32]byte) ([32]byte, *edwards25519.Scalar) { + var data []byte + data = append(data, transcript[:]...) + data = append(data, u0[:]...) + data = append(data, u1[:]...) + h := Keccak256(data) + reduced := ScReduce32(h) + var result [32]byte + copy(result[:], reduced) + s, _ := edwards25519.NewScalar().SetCanonicalBytes(reduced) + return result, s +} + +func keyFromPoint(p *edwards25519.Point) [32]byte { + var k [32]byte + copy(k[:], p.Bytes()) + return k +} + +func keyFromScalar(s *edwards25519.Scalar) [32]byte { + var k [32]byte + copy(k[:], s.Bytes()) + return k +} + +func vectorExponent(a, b []*edwards25519.Scalar, n int) *edwards25519.Point { + result := edwards25519.NewIdentityPoint() + for i := 0; i < n; i++ { + aGi := edwards25519.NewIdentityPoint().ScalarMult(a[i], Gi[i]) + bHi := edwards25519.NewIdentityPoint().ScalarMult(b[i], Hi[i]) + result = edwards25519.NewIdentityPoint().Add(result, aGi) + result = edwards25519.NewIdentityPoint().Add(result, bHi) + } + return result +} + +func weightedInnerProduct(a, b []*edwards25519.Scalar, y *edwards25519.Scalar) *edwards25519.Scalar { + result := edwards25519.NewScalar() + yPow := scalarCopy(scOne) + for i := range a { + yPow = scMul(yPow, y) + t := scMul(a[i], scMul(yPow, b[i])) + result = scalarAdd(result, t) + } + return result +} + +func computeLRWithGens(size int, yPow *edwards25519.Scalar, + G []*edwards25519.Point, Hg []*edwards25519.Point, + a []*edwards25519.Scalar, b []*edwards25519.Scalar, + c, d *edwards25519.Scalar) *edwards25519.Point { + // L or R = sum(a[i]*yPow/8*G[i] + b[i]/8*H[i]) + c/8*H + d/8*G_base + result := edwards25519.NewIdentityPoint() + for i := 0; i < size; i++ { + aYi := scMul(scMul(a[i], yPow), scInvEight) + bI := scMul(b[i], scInvEight) + aGi := edwards25519.NewIdentityPoint().ScalarMult(aYi, G[i]) + bHi := edwards25519.NewIdentityPoint().ScalarMult(bI, Hg[i]) + result = ptAdd(result, aGi) + result = ptAdd(result, bHi) + } + cH := edwards25519.NewIdentityPoint().ScalarMult(scMul(c, scInvEight), H) + dG := ptScalarBaseMult(scMul(d, scInvEight)) + result = ptAdd(result, cH) + result = ptAdd(result, dG) + return result +} + +func ptCopy(p *edwards25519.Point) *edwards25519.Point { + return edwards25519.NewIdentityPoint().Add(p, edwards25519.NewIdentityPoint()) +} + +func vectorAdd(a, b []*edwards25519.Scalar) []*edwards25519.Scalar { + r := make([]*edwards25519.Scalar, len(a)) + for i := range a { + r[i] = scalarAdd(a[i], b[i]) + } + return r +} + +func vectorScalar(v []*edwards25519.Scalar, s *edwards25519.Scalar) []*edwards25519.Scalar { + r := make([]*edwards25519.Scalar, len(v)) + for i := range v { + r[i] = scMul(v[i], s) + } + return r +} diff --git a/chain/monero/crypto/clsag.go b/chain/monero/crypto/clsag.go new file mode 100644 index 00000000..3c3c5d59 --- /dev/null +++ b/chain/monero/crypto/clsag.go @@ -0,0 +1,273 @@ +package crypto + +import ( + "fmt" + "io" + + "filippo.io/edwards25519" +) + +// CLSAGSignature represents a CLSAG ring signature. +type CLSAGSignature struct { + S []*edwards25519.Scalar // response scalars (one per ring member) + C1 *edwards25519.Scalar // initial challenge + I *edwards25519.Point // key image: p * H_p(P[l]) + D *edwards25519.Point // commitment key image: (1/8) * z * H_p(P[l]) +} + +// CLSAGContext holds parameters for CLSAG signing. +type CLSAGContext struct { + Message []byte // tx prefix hash (32 bytes) + Ring []*edwards25519.Point // P[0..n-1]: one-time public keys in the ring + CNonzero []*edwards25519.Point // C_nonzero[0..n-1]: original commitments from chain + COffset *edwards25519.Point // pseudo-output commitment for this input + SecretIndex int // position of real output in ring + SecretKey *edwards25519.Scalar // p: one-time private key + ZKey *edwards25519.Scalar // z = input_mask - pseudo_out_mask + Rand io.Reader // optional deterministic RNG +} + +// invEight is the scalar 1/8 mod L, used for scaling the commitment image D. +var invEight *edwards25519.Scalar + +func init() { + eight := make([]byte, 32) + eight[0] = 8 + eightScalar, _ := edwards25519.NewScalar().SetCanonicalBytes(eight) + invEight = edwards25519.NewScalar().Invert(eightScalar) +} + +// ComputeKeyImage computes I = x * H_p(P) for a given private key and public key. +// Uses pure Go hash_to_ec. +func ComputeKeyImage(privateKey *edwards25519.Scalar, publicKey *edwards25519.Point) *edwards25519.Point { + hpBytes := HashToECPureGo(publicKey.Bytes()) + hp, _ := edwards25519.NewIdentityPoint().SetBytes(hpBytes) + return edwards25519.NewIdentityPoint().ScalarMult(privateKey, hp) +} + +// CLSAGSign produces a CLSAG ring signature following Monero's exact algorithm. +// Reference: monero/src/ringct/rctSigs.cpp CLSAG_Gen +func CLSAGSign(ctx *CLSAGContext) (*CLSAGSignature, error) { + n := len(ctx.Ring) + if n == 0 { + return nil, fmt.Errorf("empty ring") + } + l := ctx.SecretIndex + p := ctx.SecretKey + z := ctx.ZKey + + // Compute adjusted commitments: C[i] = C_nonzero[i] - C_offset + C := make([]*edwards25519.Point, n) + negCOffset := edwards25519.NewIdentityPoint().Negate(ctx.COffset) + for i := 0; i < n; i++ { + C[i] = edwards25519.NewIdentityPoint().Add(ctx.CNonzero[i], negCOffset) + } + + // Compute H_p(P[l]) - hash to point of signer's public key + hpBytes := HashToEC(ctx.Ring[l].Bytes()) + Hp, _ := edwards25519.NewIdentityPoint().SetBytes(hpBytes) + + // Key image: I = p * H_p(P[l]) + I := edwards25519.NewIdentityPoint().ScalarMult(p, Hp) + + // Commitment image: D_full = z * H_p(P[l]), then D = (1/8) * D_full + Dfull := edwards25519.NewIdentityPoint().ScalarMult(z, Hp) + D := edwards25519.NewIdentityPoint().ScalarMult(invEight, Dfull) + + // Random nonce + a := randomScalarFrom(ctx.Rand) + + // aG = a * G + aG := edwards25519.NewGeneratorPoint().ScalarBaseMult(a) + // aH = a * H_p(P[l]) + aH := edwards25519.NewIdentityPoint().ScalarMult(a, Hp) + + // --- Compute aggregation coefficients mu_P, mu_C --- + // Uses sig.D (= D/8) in the hash, matching both prove and verify code. + muPData := make([]byte, 0, 32*(2*n+4)) + muCData := make([]byte, 0, 32*(2*n+4)) + + tag0 := make([]byte, 32) + copy(tag0, "CLSAG_agg_0") + tag1 := make([]byte, 32) + copy(tag1, "CLSAG_agg_1") + + muPData = append(muPData, tag0...) + muCData = append(muCData, tag1...) + + for i := 0; i < n; i++ { + muPData = append(muPData, ctx.Ring[i].Bytes()...) + muCData = append(muCData, ctx.Ring[i].Bytes()...) + } + for i := 0; i < n; i++ { + muPData = append(muPData, ctx.CNonzero[i].Bytes()...) + muCData = append(muCData, ctx.CNonzero[i].Bytes()...) + } + // I, sig.D (= D/8), C_offset + muPData = append(muPData, I.Bytes()...) + muPData = append(muPData, D.Bytes()...) // D here is already D/8 (sig.D) + muPData = append(muPData, ctx.COffset.Bytes()...) + muCData = append(muCData, I.Bytes()...) + muCData = append(muCData, D.Bytes()...) // D here is already D/8 (sig.D) + muCData = append(muCData, ctx.COffset.Bytes()...) + + muP := hashToScalar(muPData) + muC := hashToScalar(muCData) + + // --- Build round hash template --- + // "CLSAG_round" || P[0..n-1] || C_nonzero[0..n-1] || C_offset || message || L || R + roundPrefix := make([]byte, 0, 32*(2*n+3)) + roundTag := make([]byte, 32) + copy(roundTag, "CLSAG_round") + roundPrefix = append(roundPrefix, roundTag...) + for i := 0; i < n; i++ { + roundPrefix = append(roundPrefix, ctx.Ring[i].Bytes()...) + } + for i := 0; i < n; i++ { + roundPrefix = append(roundPrefix, ctx.CNonzero[i].Bytes()...) + } + roundPrefix = append(roundPrefix, ctx.COffset.Bytes()...) + roundPrefix = append(roundPrefix, ctx.Message...) + + // Initial challenge: hash with aG and aH + c0Data := make([]byte, 0, len(roundPrefix)+64) + c0Data = append(c0Data, roundPrefix...) + c0Data = append(c0Data, aG.Bytes()...) + c0Data = append(c0Data, aH.Bytes()...) + c := hashToScalar(c0Data) + + // Initialize s values + s := make([]*edwards25519.Scalar, n) + + // Store c1 when we wrap around to index 0 + var c1 *edwards25519.Scalar + + i := (l + 1) % n + if i == 0 { + c1 = scalarCopy(c) + } + + // --- Ring traversal --- + for i != l { + s[i] = randomScalarFrom(ctx.Rand) + + // c_p = mu_P * c, c_c = mu_C * c + cP := scalarMul(muP, c) + cC := scalarMul(muC, c) + + // L = s[i]*G + c_p*P[i] + c_c*C[i] + siG := edwards25519.NewGeneratorPoint().ScalarBaseMult(s[i]) + cpPi := edwards25519.NewIdentityPoint().ScalarMult(cP, ctx.Ring[i]) + ccCi := edwards25519.NewIdentityPoint().ScalarMult(cC, C[i]) + L := edwards25519.NewIdentityPoint().Add(siG, cpPi) + L = edwards25519.NewIdentityPoint().Add(L, ccCi) + + // R = s[i]*H_p(P[i]) + c_p*I + c_c*Dfull + // Prove code line 268: D_precomp from D (full, NOT D/8) + // Verify code line 897-902: D_precomp from 8*sig.D = D (full) + // Both use the FULL D for ring computation. + hpPiBytes := HashToEC(ctx.Ring[i].Bytes()) + hpPi, _ := edwards25519.NewIdentityPoint().SetBytes(hpPiBytes) + siHp := edwards25519.NewIdentityPoint().ScalarMult(s[i], hpPi) + cpI := edwards25519.NewIdentityPoint().ScalarMult(cP, I) + ccDfull := edwards25519.NewIdentityPoint().ScalarMult(cC, Dfull) + R := edwards25519.NewIdentityPoint().Add(siHp, cpI) + R = edwards25519.NewIdentityPoint().Add(R, ccDfull) + + // Next challenge + cData := make([]byte, 0, len(roundPrefix)+64) + cData = append(cData, roundPrefix...) + cData = append(cData, L.Bytes()...) + cData = append(cData, R.Bytes()...) + c = hashToScalar(cData) + + i = (i + 1) % n + if i == 0 { + c1 = scalarCopy(c) + } + } + + // --- Close the ring --- + // s[l] = a - c * (mu_P * p + mu_C * z) + muPp := scalarMul(muP, p) + muCz := scalarMul(muC, z) + secret := scalarAdd(muPp, muCz) + s[l] = scalarSub(a, scalarMul(c, secret)) + + if c1 == nil { + // This happens when l == n-1 and we never wrapped to 0 + // c1 should be the challenge computed after s[n-1] + // which is the initial c we started with from (l+1) % n = 0 + // Actually if l = n-1, then i starts at 0, and c1 is set immediately. + // So c1 should always be set. Just in case: + c1 = scalarCopy(c) + } + + return &CLSAGSignature{ + S: s, + C1: c1, + I: I, + D: D, + }, nil +} + +// SerializeCLSAGWithKeyImage serializes a CLSAG signature + key image. +// Format: key_image(32) || s[0](32) || ... || s[n-1](32) || c1(32) || D(32) +func SerializeCLSAGWithKeyImage(sig *CLSAGSignature, keyImage *edwards25519.Point) []byte { + var out []byte + out = append(out, keyImage.Bytes()...) + for _, s := range sig.S { + out = append(out, s.Bytes()...) + } + out = append(out, sig.C1.Bytes()...) + out = append(out, sig.D.Bytes()...) + return out +} + +// DeserializeCLSAG parses a CLSAG signature + key image from bytes. +// Returns (signature, keyImage, error). +func DeserializeCLSAG(data []byte, ringSize int) (*CLSAGSignature, []byte, error) { + expected := 32 + ringSize*32 + 32 + 32 // keyImage + s[] + c1 + D + if len(data) != expected { + return nil, nil, fmt.Errorf("expected %d bytes, got %d", expected, len(data)) + } + + pos := 0 + keyImage := data[pos : pos+32] + pos += 32 + + s := make([]*edwards25519.Scalar, ringSize) + for i := 0; i < ringSize; i++ { + s[i], _ = edwards25519.NewScalar().SetCanonicalBytes(data[pos : pos+32]) + pos += 32 + } + + c1, _ := edwards25519.NewScalar().SetCanonicalBytes(data[pos : pos+32]) + pos += 32 + + D, _ := edwards25519.NewIdentityPoint().SetBytes(data[pos : pos+32]) + + // Reconstruct I from key image bytes + I, _ := edwards25519.NewIdentityPoint().SetBytes(keyImage) + + return &CLSAGSignature{S: s, C1: c1, I: I, D: D}, keyImage, nil +} + +// SerializeCLSAG serializes a CLSAG signature to bytes. +// Format: s[0] || s[1] || ... || s[n-1] || c1 || D +func (sig *CLSAGSignature) Serialize() []byte { + var out []byte + for _, s := range sig.S { + out = append(out, s.Bytes()...) + } + out = append(out, sig.C1.Bytes()...) + out = append(out, sig.D.Bytes()...) + return out +} + +func hashToScalar(data []byte) *edwards25519.Scalar { + hash := Keccak256(data) + reduced := ScReduce32(hash) + s, _ := edwards25519.NewScalar().SetCanonicalBytes(reduced) + return s +} diff --git a/chain/monero/crypto/clsag_test.go b/chain/monero/crypto/clsag_test.go new file mode 100644 index 00000000..e60ea99d --- /dev/null +++ b/chain/monero/crypto/clsag_test.go @@ -0,0 +1,111 @@ +package crypto + +import ( + "testing" + + "filippo.io/edwards25519" + "github.com/stretchr/testify/require" +) + +func TestCLSAGSignAndVerify(t *testing.T) { + // Create a simple ring of size 4 with random keys + n := 4 + secretIndex := 2 + + // Generate random ring members + ring := make([]*edwards25519.Point, n) + commitments := make([]*edwards25519.Point, n) + + for i := 0; i < n; i++ { + sk := randomScalar() + ring[i] = edwards25519.NewGeneratorPoint().ScalarBaseMult(sk) + mask := randomScalar() + commitments[i], _ = PedersenCommit(uint64(1000*(i+1)), mask.Bytes()) + } + + // Real signer's keys + privKey := randomScalar() + ring[secretIndex] = edwards25519.NewGeneratorPoint().ScalarBaseMult(privKey) + + // Real signer's commitment + amount := uint64(5000) + inputMask := randomScalar() + commitments[secretIndex], _ = PedersenCommit(amount, inputMask.Bytes()) + + // Pseudo-output commitment (must balance) + pseudoMask := randomScalar() + cOffset, _ := PedersenCommit(amount, pseudoMask.Bytes()) + + // z = input_mask - pseudo_mask + z := scalarSub(inputMask, pseudoMask) + + message := Keccak256([]byte("test message for CLSAG")) + + ctx := &CLSAGContext{ + Message: message, + Ring: ring, + CNonzero: commitments, + COffset: cOffset, + SecretIndex: secretIndex, + SecretKey: privKey, + ZKey: z, + } + + sig, err := CLSAGSign(ctx) + require.NoError(t, err) + require.NotNil(t, sig) + require.Len(t, sig.S, n) + + // Verify + valid := CLSAGVerify(message, ring, commitments, cOffset, sig) + require.True(t, valid, "CLSAG signature should verify") + + // Verify with wrong message fails + wrongMsg := Keccak256([]byte("wrong message")) + valid2 := CLSAGVerify(wrongMsg, ring, commitments, cOffset, sig) + require.False(t, valid2, "CLSAG with wrong message should not verify") +} + +func TestCLSAGRingSize16(t *testing.T) { + // Test with realistic ring size of 16 + n := 16 + secretIndex := 7 + + ring := make([]*edwards25519.Point, n) + commitments := make([]*edwards25519.Point, n) + for i := 0; i < n; i++ { + sk := randomScalar() + ring[i] = edwards25519.NewGeneratorPoint().ScalarBaseMult(sk) + mask := randomScalar() + commitments[i], _ = PedersenCommit(uint64(1000*(i+1)), mask.Bytes()) + } + + privKey := randomScalar() + ring[secretIndex] = edwards25519.NewGeneratorPoint().ScalarBaseMult(privKey) + + amount := uint64(43910000000) // our deposit amount + inputMask := randomScalar() + commitments[secretIndex], _ = PedersenCommit(amount, inputMask.Bytes()) + + pseudoMask := randomScalar() + cOffset, _ := PedersenCommit(amount, pseudoMask.Bytes()) + z := scalarSub(inputMask, pseudoMask) + + message := Keccak256([]byte("ring size 16 test")) + + ctx := &CLSAGContext{ + Message: message, + Ring: ring, + CNonzero: commitments, + COffset: cOffset, + SecretIndex: secretIndex, + SecretKey: privKey, + ZKey: z, + } + + sig, err := CLSAGSign(ctx) + require.NoError(t, err) + + valid := CLSAGVerify(message, ring, commitments, cOffset, sig) + require.True(t, valid, "CLSAG with ring size 16 should verify") +} diff --git a/chain/monero/crypto/clsag_verify.go b/chain/monero/crypto/clsag_verify.go new file mode 100644 index 00000000..ee9b3103 --- /dev/null +++ b/chain/monero/crypto/clsag_verify.go @@ -0,0 +1,110 @@ +package crypto + +import ( + "filippo.io/edwards25519" +) + +// CLSAGVerify verifies a CLSAG ring signature. +// Returns true if the signature is valid. +func CLSAGVerify( + message []byte, + ring []*edwards25519.Point, + cNonzero []*edwards25519.Point, + cOffset *edwards25519.Point, + sig *CLSAGSignature, +) bool { + n := len(ring) + if n == 0 || n != len(sig.S) || n != len(cNonzero) { + return false + } + + I := sig.I + // D_8 = 8 * sig.D + eight := make([]byte, 32) + eight[0] = 8 + eightScalar, _ := edwards25519.NewScalar().SetCanonicalBytes(eight) + D8 := edwards25519.NewIdentityPoint().ScalarMult(eightScalar, sig.D) + + // Adjusted commitments: C[i] = C_nonzero[i] - C_offset + C := make([]*edwards25519.Point, n) + negCOffset := edwards25519.NewIdentityPoint().Negate(cOffset) + for i := 0; i < n; i++ { + C[i] = edwards25519.NewIdentityPoint().Add(cNonzero[i], negCOffset) + } + + // Aggregation hashes + muPData := make([]byte, 0, 32*(2*n+4)) + muCData := make([]byte, 0, 32*(2*n+4)) + tag0 := make([]byte, 32) + copy(tag0, "CLSAG_agg_0") + tag1 := make([]byte, 32) + copy(tag1, "CLSAG_agg_1") + muPData = append(muPData, tag0...) + muCData = append(muCData, tag1...) + for i := 0; i < n; i++ { + muPData = append(muPData, ring[i].Bytes()...) + muCData = append(muCData, ring[i].Bytes()...) + } + for i := 0; i < n; i++ { + muPData = append(muPData, cNonzero[i].Bytes()...) + muCData = append(muCData, cNonzero[i].Bytes()...) + } + muPData = append(muPData, I.Bytes()...) + muPData = append(muPData, sig.D.Bytes()...) + muPData = append(muPData, cOffset.Bytes()...) + muCData = append(muCData, I.Bytes()...) + muCData = append(muCData, sig.D.Bytes()...) + muCData = append(muCData, cOffset.Bytes()...) + + muP := hashToScalar(muPData) + muC := hashToScalar(muCData) + + // Round hash prefix + roundPrefix := make([]byte, 0, 32*(2*n+3)) + roundTag := make([]byte, 32) + copy(roundTag, "CLSAG_round") + roundPrefix = append(roundPrefix, roundTag...) + for i := 0; i < n; i++ { + roundPrefix = append(roundPrefix, ring[i].Bytes()...) + } + for i := 0; i < n; i++ { + roundPrefix = append(roundPrefix, cNonzero[i].Bytes()...) + } + roundPrefix = append(roundPrefix, cOffset.Bytes()...) + roundPrefix = append(roundPrefix, message...) + + // Start from c1, walk the ring + c := scalarCopy(sig.C1) + + for i := 0; i < n; i++ { + cP := scalarMul(muP, c) + cC := scalarMul(muC, c) + + // L = s[i]*G + c_p*P[i] + c_c*C[i] + siG := edwards25519.NewGeneratorPoint().ScalarBaseMult(sig.S[i]) + cpPi := edwards25519.NewIdentityPoint().ScalarMult(cP, ring[i]) + ccCi := edwards25519.NewIdentityPoint().ScalarMult(cC, C[i]) + L := edwards25519.NewIdentityPoint().Add(siG, cpPi) + L = edwards25519.NewIdentityPoint().Add(L, ccCi) + + // R = s[i]*H_p(P[i]) + c_p*I + c_c*D8 + hpPiBytes := HashToEC(ring[i].Bytes()) + hpPi, _ := edwards25519.NewIdentityPoint().SetBytes(hpPiBytes) + siHp := edwards25519.NewIdentityPoint().ScalarMult(sig.S[i], hpPi) + cpI := edwards25519.NewIdentityPoint().ScalarMult(cP, I) + ccD8 := edwards25519.NewIdentityPoint().ScalarMult(cC, D8) + R := edwards25519.NewIdentityPoint().Add(siHp, cpI) + R = edwards25519.NewIdentityPoint().Add(R, ccD8) + + // Next challenge + cData := make([]byte, 0, len(roundPrefix)+64) + cData = append(cData, roundPrefix...) + cData = append(cData, L.Bytes()...) + cData = append(cData, R.Bytes()...) + c = hashToScalar(cData) + } + + // Check: c should equal sig.C1 + diff := scalarSub(c, sig.C1) + return diff.Equal(edwards25519.NewScalar()) == 1 +} diff --git a/chain/monero/crypto/cref/bp_plus_wrapper.h b/chain/monero/crypto/cref/bp_plus_wrapper.h new file mode 100644 index 00000000..5e7ef667 --- /dev/null +++ b/chain/monero/crypto/cref/bp_plus_wrapper.h @@ -0,0 +1,48 @@ +// C header for Monero BP+ wrapper - for use with CGO +#ifndef BP_PLUS_WRAPPER_H +#define BP_PLUS_WRAPPER_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// Generate a BP+ range proof for `count` amounts. +// +// amounts: array of `count` uint64_t values +// masks: array of `count` * 32 bytes (each mask/gamma is a 32-byte ed25519 scalar) +// count: number of amounts (1 to 16) +// proof_out: output buffer (caller allocates, recommended 8192 bytes) +// proof_len: [in] capacity of proof_out; [out] actual bytes written +// +// Returns 0 on success, negative on error: +// -1: invalid arguments +// -2: output buffer too small +// -3: internal exception +// -4: unknown error +int monero_bp_plus_prove( + const uint64_t *amounts, + const unsigned char *masks, + int count, + unsigned char *proof_out, + int *proof_len); + +// Verify a BP+ range proof. +// +// proof_data: serialized proof bytes (from monero_bp_plus_prove) +// proof_len: length of proof_data +// +// Returns: +// 1: proof is valid +// 0: proof is invalid +// <0: error +int monero_bp_plus_verify( + const unsigned char *proof_data, + int proof_len); + +#ifdef __cplusplus +} +#endif + +#endif // BP_PLUS_WRAPPER_H diff --git a/chain/monero/crypto/cref/crypto-ops-data.c b/chain/monero/crypto/cref/crypto-ops-data.c new file mode 100644 index 00000000..edaa4644 --- /dev/null +++ b/chain/monero/crypto/cref/crypto-ops-data.c @@ -0,0 +1,879 @@ +// Copyright (c) 2014-2024, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#include + +#include "crypto-ops.h" + +/* sqrt(x) is such an integer y that 0 <= y <= p - 1, y % 2 = 0, and y^2 = x (mod p). */ +/* d = -121665 / 121666 */ +const fe fe_d = {-10913610, 13857413, -15372611, 6949391, 114729, -8787816, -6275908, -3247719, -18696448, -12055116}; /* d */ +const fe fe_sqrtm1 = {-32595792, -7943725, 9377950, 3500415, 12389472, -272473, -25146209, -2005654, 326686, 11406482}; /* sqrt(-1) */ +const fe fe_d2 = {-21827239, -5839606, -30745221, 13898782, 229458, 15978800, -12551817, -6495438, 29715968, 9444199}; /* 2 * d */ + +/* base[i][j] = (j+1)*256^i*B */ +const ge_precomp ge_base[32][8] = { + { + {{25967493, -14356035, 29566456, 3660896, -12694345, 4014787, 27544626, -11754271, -6079156, 2047605}, + {-12545711, 934262, -2722910, 3049990, -727428, 9406986, 12720692, 5043384, 19500929, -15469378}, + {-8738181, 4489570, 9688441, -14785194, 10184609, -12363380, 29287919, 11864899, -24514362, -4438546}}, + {{-12815894, -12976347, -21581243, 11784320, -25355658, -2750717, -11717903, -3814571, -358445, -10211303}, + {-21703237, 6903825, 27185491, 6451973, -29577724, -9554005, -15616551, 11189268, -26829678, -5319081}, + {26966642, 11152617, 32442495, 15396054, 14353839, -12752335, -3128826, -9541118, -15472047, -4166697}}, + {{15636291, -9688557, 24204773, -7912398, 616977, -16685262, 27787600, -14772189, 28944400, -1550024}, + {16568933, 4717097, -11556148, -1102322, 15682896, -11807043, 16354577, -11775962, 7689662, 11199574}, + {30464156, -5976125, -11779434, -15670865, 23220365, 15915852, 7512774, 10017326, -17749093, -9920357}}, + {{-17036878, 13921892, 10945806, -6033431, 27105052, -16084379, -28926210, 15006023, 3284568, -6276540}, + {23599295, -8306047, -11193664, -7687416, 13236774, 10506355, 7464579, 9656445, 13059162, 10374397}, + {7798556, 16710257, 3033922, 2874086, 28997861, 2835604, 32406664, -3839045, -641708, -101325}}, + {{10861363, 11473154, 27284546, 1981175, -30064349, 12577861, 32867885, 14515107, -15438304, 10819380}, + {4708026, 6336745, 20377586, 9066809, -11272109, 6594696, -25653668, 12483688, -12668491, 5581306}, + {19563160, 16186464, -29386857, 4097519, 10237984, -4348115, 28542350, 13850243, -23678021, -15815942}}, + {{-15371964, -12862754, 32573250, 4720197, -26436522, 5875511, -19188627, -15224819, -9818940, -12085777}, + {-8549212, 109983, 15149363, 2178705, 22900618, 4543417, 3044240, -15689887, 1762328, 14866737}, + {-18199695, -15951423, -10473290, 1707278, -17185920, 3916101, -28236412, 3959421, 27914454, 4383652}}, + {{5153746, 9909285, 1723747, -2777874, 30523605, 5516873, 19480852, 5230134, -23952439, -15175766}, + {-30269007, -3463509, 7665486, 10083793, 28475525, 1649722, 20654025, 16520125, 30598449, 7715701}, + {28881845, 14381568, 9657904, 3680757, -20181635, 7843316, -31400660, 1370708, 29794553, -1409300}}, + {{14499471, -2729599, -33191113, -4254652, 28494862, 14271267, 30290735, 10876454, -33154098, 2381726}, + {-7195431, -2655363, -14730155, 462251, -27724326, 3941372, -6236617, 3696005, -32300832, 15351955}, + {27431194, 8222322, 16448760, -3907995, -18707002, 11938355, -32961401, -2970515, 29551813, 10109425}} + }, { + {{-13657040, -13155431, -31283750, 11777098, 21447386, 6519384, -2378284, -1627556, 10092783, -4764171}, + {27939166, 14210322, 4677035, 16277044, -22964462, -12398139, -32508754, 12005538, -17810127, 12803510}, + {17228999, -15661624, -1233527, 300140, -1224870, -11714777, 30364213, -9038194, 18016357, 4397660}}, + {{-10958843, -7690207, 4776341, -14954238, 27850028, -15602212, -26619106, 14544525, -17477504, 982639}, + {29253598, 15796703, -2863982, -9908884, 10057023, 3163536, 7332899, -4120128, -21047696, 9934963}, + {5793303, 16271923, -24131614, -10116404, 29188560, 1206517, -14747930, 4559895, -30123922, -10897950}}, + {{-27643952, -11493006, 16282657, -11036493, 28414021, -15012264, 24191034, 4541697, -13338309, 5500568}, + {12650548, -1497113, 9052871, 11355358, -17680037, -8400164, -17430592, 12264343, 10874051, 13524335}, + {25556948, -3045990, 714651, 2510400, 23394682, -10415330, 33119038, 5080568, -22528059, 5376628}}, + {{-26088264, -4011052, -17013699, -3537628, -6726793, 1920897, -22321305, -9447443, 4535768, 1569007}, + {-2255422, 14606630, -21692440, -8039818, 28430649, 8775819, -30494562, 3044290, 31848280, 12543772}, + {-22028579, 2943893, -31857513, 6777306, 13784462, -4292203, -27377195, -2062731, 7718482, 14474653}}, + {{2385315, 2454213, -22631320, 46603, -4437935, -15680415, 656965, -7236665, 24316168, -5253567}, + {13741529, 10911568, -33233417, -8603737, -20177830, -1033297, 33040651, -13424532, -20729456, 8321686}, + {21060490, -2212744, 15712757, -4336099, 1639040, 10656336, 23845965, -11874838, -9984458, 608372}}, + {{-13672732, -15087586, -10889693, -7557059, -6036909, 11305547, 1123968, -6780577, 27229399, 23887}, + {-23244140, -294205, -11744728, 14712571, -29465699, -2029617, 12797024, -6440308, -1633405, 16678954}, + {-29500620, 4770662, -16054387, 14001338, 7830047, 9564805, -1508144, -4795045, -17169265, 4904953}}, + {{24059557, 14617003, 19037157, -15039908, 19766093, -14906429, 5169211, 16191880, 2128236, -4326833}, + {-16981152, 4124966, -8540610, -10653797, 30336522, -14105247, -29806336, 916033, -6882542, -2986532}, + {-22630907, 12419372, -7134229, -7473371, -16478904, 16739175, 285431, 2763829, 15736322, 4143876}}, + {{2379352, 11839345, -4110402, -5988665, 11274298, 794957, 212801, -14594663, 23527084, -16458268}, + {33431127, -11130478, -17838966, -15626900, 8909499, 8376530, -32625340, 4087881, -15188911, -14416214}, + {1767683, 7197987, -13205226, -2022635, -13091350, 448826, 5799055, 4357868, -4774191, -16323038}} + }, { + {{6721966, 13833823, -23523388, -1551314, 26354293, -11863321, 23365147, -3949732, 7390890, 2759800}, + {4409041, 2052381, 23373853, 10530217, 7676779, -12885954, 21302353, -4264057, 1244380, -12919645}, + {-4421239, 7169619, 4982368, -2957590, 30256825, -2777540, 14086413, 9208236, 15886429, 16489664}}, + {{1996075, 10375649, 14346367, 13311202, -6874135, -16438411, -13693198, 398369, -30606455, -712933}, + {-25307465, 9795880, -2777414, 14878809, -33531835, 14780363, 13348553, 12076947, -30836462, 5113182}, + {-17770784, 11797796, 31950843, 13929123, -25888302, 12288344, -30341101, -7336386, 13847711, 5387222}}, + {{-18582163, -3416217, 17824843, -2340966, 22744343, -10442611, 8763061, 3617786, -19600662, 10370991}, + {20246567, -14369378, 22358229, -543712, 18507283, -10413996, 14554437, -8746092, 32232924, 16763880}, + {9648505, 10094563, 26416693, 14745928, -30374318, -6472621, 11094161, 15689506, 3140038, -16510092}}, + {{-16160072, 5472695, 31895588, 4744994, 8823515, 10365685, -27224800, 9448613, -28774454, 366295}, + {19153450, 11523972, -11096490, -6503142, -24647631, 5420647, 28344573, 8041113, 719605, 11671788}, + {8678025, 2694440, -6808014, 2517372, 4964326, 11152271, -15432916, -15266516, 27000813, -10195553}}, + {{-15157904, 7134312, 8639287, -2814877, -7235688, 10421742, 564065, 5336097, 6750977, -14521026}, + {11836410, -3979488, 26297894, 16080799, 23455045, 15735944, 1695823, -8819122, 8169720, 16220347}, + {-18115838, 8653647, 17578566, -6092619, -8025777, -16012763, -11144307, -2627664, -5990708, -14166033}}, + {{-23308498, -10968312, 15213228, -10081214, -30853605, -11050004, 27884329, 2847284, 2655861, 1738395}, + {-27537433, -14253021, -25336301, -8002780, -9370762, 8129821, 21651608, -3239336, -19087449, -11005278}, + {1533110, 3437855, 23735889, 459276, 29970501, 11335377, 26030092, 5821408, 10478196, 8544890}}, + {{32173121, -16129311, 24896207, 3921497, 22579056, -3410854, 19270449, 12217473, 17789017, -3395995}, + {-30552961, -2228401, -15578829, -10147201, 13243889, 517024, 15479401, -3853233, 30460520, 1052596}, + {-11614875, 13323618, 32618793, 8175907, -15230173, 12596687, 27491595, -4612359, 3179268, -9478891}}, + {{31947069, -14366651, -4640583, -15339921, -15125977, -6039709, -14756777, -16411740, 19072640, -9511060}, + {11685058, 11822410, 3158003, -13952594, 33402194, -4165066, 5977896, -5215017, 473099, 5040608}, + {-20290863, 8198642, -27410132, 11602123, 1290375, -2799760, 28326862, 1721092, -19558642, -3131606}} + }, { + {{7881532, 10687937, 7578723, 7738378, -18951012, -2553952, 21820786, 8076149, -27868496, 11538389}, + {-19935666, 3899861, 18283497, -6801568, -15728660, -11249211, 8754525, 7446702, -5676054, 5797016}, + {-11295600, -3793569, -15782110, -7964573, 12708869, -8456199, 2014099, -9050574, -2369172, -5877341}}, + {{-22472376, -11568741, -27682020, 1146375, 18956691, 16640559, 1192730, -3714199, 15123619, 10811505}, + {14352098, -3419715, -18942044, 10822655, 32750596, 4699007, -70363, 15776356, -28886779, -11974553}, + {-28241164, -8072475, -4978962, -5315317, 29416931, 1847569, -20654173, -16484855, 4714547, -9600655}}, + {{15200332, 8368572, 19679101, 15970074, -31872674, 1959451, 24611599, -4543832, -11745876, 12340220}, + {12876937, -10480056, 33134381, 6590940, -6307776, 14872440, 9613953, 8241152, 15370987, 9608631}, + {-4143277, -12014408, 8446281, -391603, 4407738, 13629032, -7724868, 15866074, -28210621, -8814099}}, + {{26660628, -15677655, 8393734, 358047, -7401291, 992988, -23904233, 858697, 20571223, 8420556}, + {14620715, 13067227, -15447274, 8264467, 14106269, 15080814, 33531827, 12516406, -21574435, -12476749}, + {236881, 10476226, 57258, -14677024, 6472998, 2466984, 17258519, 7256740, 8791136, 15069930}}, + {{1276410, -9371918, 22949635, -16322807, -23493039, -5702186, 14711875, 4874229, -30663140, -2331391}, + {5855666, 4990204, -13711848, 7294284, -7804282, 1924647, -1423175, -7912378, -33069337, 9234253}, + {20590503, -9018988, 31529744, -7352666, -2706834, 10650548, 31559055, -11609587, 18979186, 13396066}}, + {{24474287, 4968103, 22267082, 4407354, 24063882, -8325180, -18816887, 13594782, 33514650, 7021958}, + {-11566906, -6565505, -21365085, 15928892, -26158305, 4315421, -25948728, -3916677, -21480480, 12868082}, + {-28635013, 13504661, 19988037, -2132761, 21078225, 6443208, -21446107, 2244500, -12455797, -8089383}}, + {{-30595528, 13793479, -5852820, 319136, -25723172, -6263899, 33086546, 8957937, -15233648, 5540521}, + {-11630176, -11503902, -8119500, -7643073, 2620056, 1022908, -23710744, -1568984, -16128528, -14962807}, + {23152971, 775386, 27395463, 14006635, -9701118, 4649512, 1689819, 892185, -11513277, -15205948}}, + {{9770129, 9586738, 26496094, 4324120, 1556511, -3550024, 27453819, 4763127, -19179614, 5867134}, + {-32765025, 1927590, 31726409, -4753295, 23962434, -16019500, 27846559, 5931263, -29749703, -16108455}, + {27461885, -2977536, 22380810, 1815854, -23033753, -3031938, 7283490, -15148073, -19526700, 7734629}} + }, { + {{-8010264, -9590817, -11120403, 6196038, 29344158, -13430885, 7585295, -3176626, 18549497, 15302069}, + {-32658337, -6171222, -7672793, -11051681, 6258878, 13504381, 10458790, -6418461, -8872242, 8424746}, + {24687205, 8613276, -30667046, -3233545, 1863892, -1830544, 19206234, 7134917, -11284482, -828919}}, + {{11334899, -9218022, 8025293, 12707519, 17523892, -10476071, 10243738, -14685461, -5066034, 16498837}, + {8911542, 6887158, -9584260, -6958590, 11145641, -9543680, 17303925, -14124238, 6536641, 10543906}, + {-28946384, 15479763, -17466835, 568876, -1497683, 11223454, -2669190, -16625574, -27235709, 8876771}}, + {{-25742899, -12566864, -15649966, -846607, -33026686, -796288, -33481822, 15824474, -604426, -9039817}, + {10330056, 70051, 7957388, -9002667, 9764902, 15609756, 27698697, -4890037, 1657394, 3084098}, + {10477963, -7470260, 12119566, -13250805, 29016247, -5365589, 31280319, 14396151, -30233575, 15272409}}, + {{-12288309, 3169463, 28813183, 16658753, 25116432, -5630466, -25173957, -12636138, -25014757, 1950504}, + {-26180358, 9489187, 11053416, -14746161, -31053720, 5825630, -8384306, -8767532, 15341279, 8373727}, + {28685821, 7759505, -14378516, -12002860, -31971820, 4079242, 298136, -10232602, -2878207, 15190420}}, + {{-32932876, 13806336, -14337485, -15794431, -24004620, 10940928, 8669718, 2742393, -26033313, -6875003}, + {-1580388, -11729417, -25979658, -11445023, -17411874, -10912854, 9291594, -16247779, -12154742, 6048605}, + {-30305315, 14843444, 1539301, 11864366, 20201677, 1900163, 13934231, 5128323, 11213262, 9168384}}, + {{-26280513, 11007847, 19408960, -940758, -18592965, -4328580, -5088060, -11105150, 20470157, -16398701}, + {-23136053, 9282192, 14855179, -15390078, -7362815, -14408560, -22783952, 14461608, 14042978, 5230683}, + {29969567, -2741594, -16711867, -8552442, 9175486, -2468974, 21556951, 3506042, -5933891, -12449708}}, + {{-3144746, 8744661, 19704003, 4581278, -20430686, 6830683, -21284170, 8971513, -28539189, 15326563}, + {-19464629, 10110288, -17262528, -3503892, -23500387, 1355669, -15523050, 15300988, -20514118, 9168260}, + {-5353335, 4488613, -23803248, 16314347, 7780487, -15638939, -28948358, 9601605, 33087103, -9011387}}, + {{-19443170, -15512900, -20797467, -12445323, -29824447, 10229461, -27444329, -15000531, -5996870, 15664672}, + {23294591, -16632613, -22650781, -8470978, 27844204, 11461195, 13099750, -2460356, 18151676, 13417686}, + {-24722913, -4176517, -31150679, 5988919, -26858785, 6685065, 1661597, -12551441, 15271676, -15452665}} + }, { + {{11433042, -13228665, 8239631, -5279517, -1985436, -725718, -18698764, 2167544, -6921301, -13440182}, + {-31436171, 15575146, 30436815, 12192228, -22463353, 9395379, -9917708, -8638997, 12215110, 12028277}, + {14098400, 6555944, 23007258, 5757252, -15427832, -12950502, 30123440, 4617780, -16900089, -655628}}, + {{-4026201, -15240835, 11893168, 13718664, -14809462, 1847385, -15819999, 10154009, 23973261, -12684474}, + {-26531820, -3695990, -1908898, 2534301, -31870557, -16550355, 18341390, -11419951, 32013174, -10103539}, + {-25479301, 10876443, -11771086, -14625140, -12369567, 1838104, 21911214, 6354752, 4425632, -837822}}, + {{-10433389, -14612966, 22229858, -3091047, -13191166, 776729, -17415375, -12020462, 4725005, 14044970}, + {19268650, -7304421, 1555349, 8692754, -21474059, -9910664, 6347390, -1411784, -19522291, -16109756}, + {-24864089, 12986008, -10898878, -5558584, -11312371, -148526, 19541418, 8180106, 9282262, 10282508}}, + {{-26205082, 4428547, -8661196, -13194263, 4098402, -14165257, 15522535, 8372215, 5542595, -10702683}, + {-10562541, 14895633, 26814552, -16673850, -17480754, -2489360, -2781891, 6993761, -18093885, 10114655}, + {-20107055, -929418, 31422704, 10427861, -7110749, 6150669, -29091755, -11529146, 25953725, -106158}}, + {{-4234397, -8039292, -9119125, 3046000, 2101609, -12607294, 19390020, 6094296, -3315279, 12831125}, + {-15998678, 7578152, 5310217, 14408357, -33548620, -224739, 31575954, 6326196, 7381791, -2421839}, + {-20902779, 3296811, 24736065, -16328389, 18374254, 7318640, 6295303, 8082724, -15362489, 12339664}}, + {{27724736, 2291157, 6088201, -14184798, 1792727, 5857634, 13848414, 15768922, 25091167, 14856294}, + {-18866652, 8331043, 24373479, 8541013, -701998, -9269457, 12927300, -12695493, -22182473, -9012899}, + {-11423429, -5421590, 11632845, 3405020, 30536730, -11674039, -27260765, 13866390, 30146206, 9142070}}, + {{3924129, -15307516, -13817122, -10054960, 12291820, -668366, -27702774, 9326384, -8237858, 4171294}, + {-15921940, 16037937, 6713787, 16606682, -21612135, 2790944, 26396185, 3731949, 345228, -5462949}, + {-21327538, 13448259, 25284571, 1143661, 20614966, -8849387, 2031539, -12391231, -16253183, -13582083}}, + {{31016211, -16722429, 26371392, -14451233, -5027349, 14854137, 17477601, 3842657, 28012650, -16405420}, + {-5075835, 9368966, -8562079, -4600902, -15249953, 6970560, -9189873, 16292057, -8867157, 3507940}, + {29439664, 3537914, 23333589, 6997794, -17555561, -11018068, -15209202, -15051267, -9164929, 6580396}} + }, { + {{-12185861, -7679788, 16438269, 10826160, -8696817, -6235611, 17860444, -9273846, -2095802, 9304567}, + {20714564, -4336911, 29088195, 7406487, 11426967, -5095705, 14792667, -14608617, 5289421, -477127}, + {-16665533, -10650790, -6160345, -13305760, 9192020, -1802462, 17271490, 12349094, 26939669, -3752294}}, + {{-12889898, 9373458, 31595848, 16374215, 21471720, 13221525, -27283495, -12348559, -3698806, 117887}, + {22263325, -6560050, 3984570, -11174646, -15114008, -566785, 28311253, 5358056, -23319780, 541964}, + {16259219, 3261970, 2309254, -15534474, -16885711, -4581916, 24134070, -16705829, -13337066, -13552195}}, + {{9378160, -13140186, -22845982, -12745264, 28198281, -7244098, -2399684, -717351, 690426, 14876244}, + {24977353, -314384, -8223969, -13465086, 28432343, -1176353, -13068804, -12297348, -22380984, 6618999}, + {-1538174, 11685646, 12944378, 13682314, -24389511, -14413193, 8044829, -13817328, 32239829, -5652762}}, + {{-18603066, 4762990, -926250, 8885304, -28412480, -3187315, 9781647, -10350059, 32779359, 5095274}, + {-33008130, -5214506, -32264887, -3685216, 9460461, -9327423, -24601656, 14506724, 21639561, -2630236}, + {-16400943, -13112215, 25239338, 15531969, 3987758, -4499318, -1289502, -6863535, 17874574, 558605}}, + {{-13600129, 10240081, 9171883, 16131053, -20869254, 9599700, 33499487, 5080151, 2085892, 5119761}, + {-22205145, -2519528, -16381601, 414691, -25019550, 2170430, 30634760, -8363614, -31999993, -5759884}, + {-6845704, 15791202, 8550074, -1312654, 29928809, -12092256, 27534430, -7192145, -22351378, 12961482}}, + {{-24492060, -9570771, 10368194, 11582341, -23397293, -2245287, 16533930, 8206996, -30194652, -5159638}, + {-11121496, -3382234, 2307366, 6362031, -135455, 8868177, -16835630, 7031275, 7589640, 8945490}, + {-32152748, 8917967, 6661220, -11677616, -1192060, -15793393, 7251489, -11182180, 24099109, -14456170}}, + {{5019558, -7907470, 4244127, -14714356, -26933272, 6453165, -19118182, -13289025, -6231896, -10280736}, + {10853594, 10721687, 26480089, 5861829, -22995819, 1972175, -1866647, -10557898, -3363451, -6441124}, + {-17002408, 5906790, 221599, -6563147, 7828208, -13248918, 24362661, -2008168, -13866408, 7421392}}, + {{8139927, -6546497, 32257646, -5890546, 30375719, 1886181, -21175108, 15441252, 28826358, -4123029}, + {6267086, 9695052, 7709135, -16603597, -32869068, -1886135, 14795160, -7840124, 13746021, -1742048}, + {28584902, 7787108, -6732942, -15050729, 22846041, -7571236, -3181936, -363524, 4771362, -8419958}} + }, { + {{24949256, 6376279, -27466481, -8174608, -18646154, -9930606, 33543569, -12141695, 3569627, 11342593}, + {26514989, 4740088, 27912651, 3697550, 19331575, -11472339, 6809886, 4608608, 7325975, -14801071}, + {-11618399, -14554430, -24321212, 7655128, -1369274, 5214312, -27400540, 10258390, -17646694, -8186692}}, + {{11431204, 15823007, 26570245, 14329124, 18029990, 4796082, -31446179, 15580664, 9280358, -3973687}, + {-160783, -10326257, -22855316, -4304997, -20861367, -13621002, -32810901, -11181622, -15545091, 4387441}, + {-20799378, 12194512, 3937617, -5805892, -27154820, 9340370, -24513992, 8548137, 20617071, -7482001}}, + {{-938825, -3930586, -8714311, 16124718, 24603125, -6225393, -13775352, -11875822, 24345683, 10325460}, + {-19855277, -1568885, -22202708, 8714034, 14007766, 6928528, 16318175, -1010689, 4766743, 3552007}, + {-21751364, -16730916, 1351763, -803421, -4009670, 3950935, 3217514, 14481909, 10988822, -3994762}}, + {{15564307, -14311570, 3101243, 5684148, 30446780, -8051356, 12677127, -6505343, -8295852, 13296005}, + {-9442290, 6624296, -30298964, -11913677, -4670981, -2057379, 31521204, 9614054, -30000824, 12074674}, + {4771191, -135239, 14290749, -13089852, 27992298, 14998318, -1413936, -1556716, 29832613, -16391035}}, + {{7064884, -7541174, -19161962, -5067537, -18891269, -2912736, 25825242, 5293297, -27122660, 13101590}, + {-2298563, 2439670, -7466610, 1719965, -27267541, -16328445, 32512469, -5317593, -30356070, -4190957}, + {-30006540, 10162316, -33180176, 3981723, -16482138, -13070044, 14413974, 9515896, 19568978, 9628812}}, + {{33053803, 199357, 15894591, 1583059, 27380243, -4580435, -17838894, -6106839, -6291786, 3437740}, + {-18978877, 3884493, 19469877, 12726490, 15913552, 13614290, -22961733, 70104, 7463304, 4176122}, + {-27124001, 10659917, 11482427, -16070381, 12771467, -6635117, -32719404, -5322751, 24216882, 5944158}}, + {{8894125, 7450974, -2664149, -9765752, -28080517, -12389115, 19345746, 14680796, 11632993, 5847885}, + {26942781, -2315317, 9129564, -4906607, 26024105, 11769399, -11518837, 6367194, -9727230, 4782140}, + {19916461, -4828410, -22910704, -11414391, 25606324, -5972441, 33253853, 8220911, 6358847, -1873857}}, + {{801428, -2081702, 16569428, 11065167, 29875704, 96627, 7908388, -4480480, -13538503, 1387155}, + {19646058, 5720633, -11416706, 12814209, 11607948, 12749789, 14147075, 15156355, -21866831, 11835260}, + {19299512, 1155910, 28703737, 14890794, 2925026, 7269399, 26121523, 15467869, -26560550, 5052483}} + }, { + {{-3017432, 10058206, 1980837, 3964243, 22160966, 12322533, -6431123, -12618185, 12228557, -7003677}, + {32944382, 14922211, -22844894, 5188528, 21913450, -8719943, 4001465, 13238564, -6114803, 8653815}, + {22865569, -4652735, 27603668, -12545395, 14348958, 8234005, 24808405, 5719875, 28483275, 2841751}}, + {{-16420968, -1113305, -327719, -12107856, 21886282, -15552774, -1887966, -315658, 19932058, -12739203}, + {-11656086, 10087521, -8864888, -5536143, -19278573, -3055912, 3999228, 13239134, -4777469, -13910208}, + {1382174, -11694719, 17266790, 9194690, -13324356, 9720081, 20403944, 11284705, -14013818, 3093230}}, + {{16650921, -11037932, -1064178, 1570629, -8329746, 7352753, -302424, 16271225, -24049421, -6691850}, + {-21911077, -5927941, -4611316, -5560156, -31744103, -10785293, 24123614, 15193618, -21652117, -16739389}, + {-9935934, -4289447, -25279823, 4372842, 2087473, 10399484, 31870908, 14690798, 17361620, 11864968}}, + {{-11307610, 6210372, 13206574, 5806320, -29017692, -13967200, -12331205, -7486601, -25578460, -16240689}, + {14668462, -12270235, 26039039, 15305210, 25515617, 4542480, 10453892, 6577524, 9145645, -6443880}, + {5974874, 3053895, -9433049, -10385191, -31865124, 3225009, -7972642, 3936128, -5652273, -3050304}}, + {{30625386, -4729400, -25555961, -12792866, -20484575, 7695099, 17097188, -16303496, -27999779, 1803632}, + {-3553091, 9865099, -5228566, 4272701, -5673832, -16689700, 14911344, 12196514, -21405489, 7047412}, + {20093277, 9920966, -11138194, -5343857, 13161587, 12044805, -32856851, 4124601, -32343828, -10257566}}, + {{-20788824, 14084654, -13531713, 7842147, 19119038, -13822605, 4752377, -8714640, -21679658, 2288038}, + {-26819236, -3283715, 29965059, 3039786, -14473765, 2540457, 29457502, 14625692, -24819617, 12570232}, + {-1063558, -11551823, 16920318, 12494842, 1278292, -5869109, -21159943, -3498680, -11974704, 4724943}}, + {{17960970, -11775534, -4140968, -9702530, -8876562, -1410617, -12907383, -8659932, -29576300, 1903856}, + {23134274, -14279132, -10681997, -1611936, 20684485, 15770816, -12989750, 3190296, 26955097, 14109738}, + {15308788, 5320727, -30113809, -14318877, 22902008, 7767164, 29425325, -11277562, 31960942, 11934971}}, + {{-27395711, 8435796, 4109644, 12222639, -24627868, 14818669, 20638173, 4875028, 10491392, 1379718}, + {-13159415, 9197841, 3875503, -8936108, -1383712, -5879801, 33518459, 16176658, 21432314, 12180697}, + {-11787308, 11500838, 13787581, -13832590, -22430679, 10140205, 1465425, 12689540, -10301319, -13872883}} + }, { + {{5414091, -15386041, -21007664, 9643570, 12834970, 1186149, -2622916, -1342231, 26128231, 6032912}, + {-26337395, -13766162, 32496025, -13653919, 17847801, -12669156, 3604025, 8316894, -25875034, -10437358}, + {3296484, 6223048, 24680646, -12246460, -23052020, 5903205, -8862297, -4639164, 12376617, 3188849}}, + {{29190488, -14659046, 27549113, -1183516, 3520066, -10697301, 32049515, -7309113, -16109234, -9852307}, + {-14744486, -9309156, 735818, -598978, -20407687, -5057904, 25246078, -15795669, 18640741, -960977}, + {-6928835, -16430795, 10361374, 5642961, 4910474, 12345252, -31638386, -494430, 10530747, 1053335}}, + {{-29265967, -14186805, -13538216, -12117373, -19457059, -10655384, -31462369, -2948985, 24018831, 15026644}, + {-22592535, -3145277, -2289276, 5953843, -13440189, 9425631, 25310643, 13003497, -2314791, -15145616}, + {-27419985, -603321, -8043984, -1669117, -26092265, 13987819, -27297622, 187899, -23166419, -2531735}}, + {{-21744398, -13810475, 1844840, 5021428, -10434399, -15911473, 9716667, 16266922, -5070217, 726099}, + {29370922, -6053998, 7334071, -15342259, 9385287, 2247707, -13661962, -4839461, 30007388, -15823341}, + {-936379, 16086691, 23751945, -543318, -1167538, -5189036, 9137109, 730663, 9835848, 4555336}}, + {{-23376435, 1410446, -22253753, -12899614, 30867635, 15826977, 17693930, 544696, -11985298, 12422646}, + {31117226, -12215734, -13502838, 6561947, -9876867, -12757670, -5118685, -4096706, 29120153, 13924425}, + {-17400879, -14233209, 19675799, -2734756, -11006962, -5858820, -9383939, -11317700, 7240931, -237388}}, + {{-31361739, -11346780, -15007447, -5856218, -22453340, -12152771, 1222336, 4389483, 3293637, -15551743}, + {-16684801, -14444245, 11038544, 11054958, -13801175, -3338533, -24319580, 7733547, 12796905, -6335822}, + {-8759414, -10817836, -25418864, 10783769, -30615557, -9746811, -28253339, 3647836, 3222231, -11160462}}, + {{18606113, 1693100, -25448386, -15170272, 4112353, 10045021, 23603893, -2048234, -7550776, 2484985}, + {9255317, -3131197, -12156162, -1004256, 13098013, -9214866, 16377220, -2102812, -19802075, -3034702}, + {-22729289, 7496160, -5742199, 11329249, 19991973, -3347502, -31718148, 9936966, -30097688, -10618797}}, + {{21878590, -5001297, 4338336, 13643897, -3036865, 13160960, 19708896, 5415497, -7360503, -4109293}, + {27736861, 10103576, 12500508, 8502413, -3413016, -9633558, 10436918, -1550276, -23659143, -8132100}, + {19492550, -12104365, -29681976, -852630, -3208171, 12403437, 30066266, 8367329, 13243957, 8709688}} + }, { + {{12015105, 2801261, 28198131, 10151021, 24818120, -4743133, -11194191, -5645734, 5150968, 7274186}, + {2831366, -12492146, 1478975, 6122054, 23825128, -12733586, 31097299, 6083058, 31021603, -9793610}, + {-2529932, -2229646, 445613, 10720828, -13849527, -11505937, -23507731, 16354465, 15067285, -14147707}}, + {{7840942, 14037873, -33364863, 15934016, -728213, -3642706, 21403988, 1057586, -19379462, -12403220}, + {915865, -16469274, 15608285, -8789130, -24357026, 6060030, -17371319, 8410997, -7220461, 16527025}, + {32922597, -556987, 20336074, -16184568, 10903705, -5384487, 16957574, 52992, 23834301, 6588044}}, + {{32752030, 11232950, 3381995, -8714866, 22652988, -10744103, 17159699, 16689107, -20314580, -1305992}, + {-4689649, 9166776, -25710296, -10847306, 11576752, 12733943, 7924251, -2752281, 1976123, -7249027}, + {21251222, 16309901, -2983015, -6783122, 30810597, 12967303, 156041, -3371252, 12331345, -8237197}}, + {{8651614, -4477032, -16085636, -4996994, 13002507, 2950805, 29054427, -5106970, 10008136, -4667901}, + {31486080, 15114593, -14261250, 12951354, 14369431, -7387845, 16347321, -13662089, 8684155, -10532952}, + {19443825, 11385320, 24468943, -9659068, -23919258, 2187569, -26263207, -6086921, 31316348, 14219878}}, + {{-28594490, 1193785, 32245219, 11392485, 31092169, 15722801, 27146014, 6992409, 29126555, 9207390}, + {32382935, 1110093, 18477781, 11028262, -27411763, -7548111, -4980517, 10843782, -7957600, -14435730}, + {2814918, 7836403, 27519878, -7868156, -20894015, -11553689, -21494559, 8550130, 28346258, 1994730}}, + {{-19578299, 8085545, -14000519, -3948622, 2785838, -16231307, -19516951, 7174894, 22628102, 8115180}, + {-30405132, 955511, -11133838, -15078069, -32447087, -13278079, -25651578, 3317160, -9943017, 930272}, + {-15303681, -6833769, 28856490, 1357446, 23421993, 1057177, 24091212, -1388970, -22765376, -10650715}}, + {{-22751231, -5303997, -12907607, -12768866, -15811511, -7797053, -14839018, -16554220, -1867018, 8398970}, + {-31969310, 2106403, -4736360, 1362501, 12813763, 16200670, 22981545, -6291273, 18009408, -15772772}, + {-17220923, -9545221, -27784654, 14166835, 29815394, 7444469, 29551787, -3727419, 19288549, 1325865}}, + {{15100157, -15835752, -23923978, -1005098, -26450192, 15509408, 12376730, -3479146, 33166107, -8042750}, + {20909231, 13023121, -9209752, 16251778, -5778415, -8094914, 12412151, 10018715, 2213263, -13878373}, + {32529814, -11074689, 30361439, -16689753, -9135940, 1513226, 22922121, 6382134, -5766928, 8371348}} + }, { + {{9923462, 11271500, 12616794, 3544722, -29998368, -1721626, 12891687, -8193132, -26442943, 10486144}, + {-22597207, -7012665, 8587003, -8257861, 4084309, -12970062, 361726, 2610596, -23921530, -11455195}, + {5408411, -1136691, -4969122, 10561668, 24145918, 14240566, 31319731, -4235541, 19985175, -3436086}}, + {{-13994457, 16616821, 14549246, 3341099, 32155958, 13648976, -17577068, 8849297, 65030, 8370684}, + {-8320926, -12049626, 31204563, 5839400, -20627288, -1057277, -19442942, 6922164, 12743482, -9800518}, + {-2361371, 12678785, 28815050, 4759974, -23893047, 4884717, 23783145, 11038569, 18800704, 255233}}, + {{-5269658, -1773886, 13957886, 7990715, 23132995, 728773, 13393847, 9066957, 19258688, -14753793}, + {-2936654, -10827535, -10432089, 14516793, -3640786, 4372541, -31934921, 2209390, -1524053, 2055794}, + {580882, 16705327, 5468415, -2683018, -30926419, -14696000, -7203346, -8994389, -30021019, 7394435}}, + {{23838809, 1822728, -15738443, 15242727, 8318092, -3733104, -21672180, -3492205, -4821741, 14799921}, + {13345610, 9759151, 3371034, -16137791, 16353039, 8577942, 31129804, 13496856, -9056018, 7402518}, + {2286874, -4435931, -20042458, -2008336, -13696227, 5038122, 11006906, -15760352, 8205061, 1607563}}, + {{14414086, -8002132, 3331830, -3208217, 22249151, -5594188, 18364661, -2906958, 30019587, -9029278}, + {-27688051, 1585953, -10775053, 931069, -29120221, -11002319, -14410829, 12029093, 9944378, 8024}, + {4368715, -3709630, 29874200, -15022983, -20230386, -11410704, -16114594, -999085, -8142388, 5640030}}, + {{10299610, 13746483, 11661824, 16234854, 7630238, 5998374, 9809887, -16694564, 15219798, -14327783}, + {27425505, -5719081, 3055006, 10660664, 23458024, 595578, -15398605, -1173195, -18342183, 9742717}, + {6744077, 2427284, 26042789, 2720740, -847906, 1118974, 32324614, 7406442, 12420155, 1994844}}, + {{14012521, -5024720, -18384453, -9578469, -26485342, -3936439, -13033478, -10909803, 24319929, -6446333}, + {16412690, -4507367, 10772641, 15929391, -17068788, -4658621, 10555945, -10484049, -30102368, -4739048}, + {22397382, -7767684, -9293161, -12792868, 17166287, -9755136, -27333065, 6199366, 21880021, -12250760}}, + {{-4283307, 5368523, -31117018, 8163389, -30323063, 3209128, 16557151, 8890729, 8840445, 4957760}, + {-15447727, 709327, -6919446, -10870178, -29777922, 6522332, -21720181, 12130072, -14796503, 5005757}, + {-2114751, -14308128, 23019042, 15765735, -25269683, 6002752, 10183197, -13239326, -16395286, -2176112}} + }, { + {{-19025756, 1632005, 13466291, -7995100, -23640451, 16573537, -32013908, -3057104, 22208662, 2000468}, + {3065073, -1412761, -25598674, -361432, -17683065, -5703415, -8164212, 11248527, -3691214, -7414184}, + {10379208, -6045554, 8877319, 1473647, -29291284, -12507580, 16690915, 2553332, -3132688, 16400289}}, + {{15716668, 1254266, -18472690, 7446274, -8448918, 6344164, -22097271, -7285580, 26894937, 9132066}, + {24158887, 12938817, 11085297, -8177598, -28063478, -4457083, -30576463, 64452, -6817084, -2692882}, + {13488534, 7794716, 22236231, 5989356, 25426474, -12578208, 2350710, -3418511, -4688006, 2364226}}, + {{16335052, 9132434, 25640582, 6678888, 1725628, 8517937, -11807024, -11697457, 15445875, -7798101}, + {29004207, -7867081, 28661402, -640412, -12794003, -7943086, 31863255, -4135540, -278050, -15759279}, + {-6122061, -14866665, -28614905, 14569919, -10857999, -3591829, 10343412, -6976290, -29828287, -10815811}}, + {{27081650, 3463984, 14099042, -4517604, 1616303, -6205604, 29542636, 15372179, 17293797, 960709}, + {20263915, 11434237, -5765435, 11236810, 13505955, -10857102, -16111345, 6493122, -19384511, 7639714}, + {-2830798, -14839232, 25403038, -8215196, -8317012, -16173699, 18006287, -16043750, 29994677, -15808121}}, + {{9769828, 5202651, -24157398, -13631392, -28051003, -11561624, -24613141, -13860782, -31184575, 709464}, + {12286395, 13076066, -21775189, -1176622, -25003198, 4057652, -32018128, -8890874, 16102007, 13205847}, + {13733362, 5599946, 10557076, 3195751, -5557991, 8536970, -25540170, 8525972, 10151379, 10394400}}, + {{4024660, -16137551, 22436262, 12276534, -9099015, -2686099, 19698229, 11743039, -33302334, 8934414}, + {-15879800, -4525240, -8580747, -2934061, 14634845, -698278, -9449077, 3137094, -11536886, 11721158}, + {17555939, -5013938, 8268606, 2331751, -22738815, 9761013, 9319229, 8835153, -9205489, -1280045}}, + {{-461409, -7830014, 20614118, 16688288, -7514766, -4807119, 22300304, 505429, 6108462, -6183415}, + {-5070281, 12367917, -30663534, 3234473, 32617080, -8422642, 29880583, -13483331, -26898490, -7867459}, + {-31975283, 5726539, 26934134, 10237677, -3173717, -605053, 24199304, 3795095, 7592688, -14992079}}, + {{21594432, -14964228, 17466408, -4077222, 32537084, 2739898, 6407723, 12018833, -28256052, 4298412}, + {-20650503, -11961496, -27236275, 570498, 3767144, -1717540, 13891942, -1569194, 13717174, 10805743}, + {-14676630, -15644296, 15287174, 11927123, 24177847, -8175568, -796431, 14860609, -26938930, -5863836}} + }, { + {{12962541, 5311799, -10060768, 11658280, 18855286, -7954201, 13286263, -12808704, -4381056, 9882022}, + {18512079, 11319350, -20123124, 15090309, 18818594, 5271736, -22727904, 3666879, -23967430, -3299429}, + {-6789020, -3146043, 16192429, 13241070, 15898607, -14206114, -10084880, -6661110, -2403099, 5276065}}, + {{30169808, -5317648, 26306206, -11750859, 27814964, 7069267, 7152851, 3684982, 1449224, 13082861}, + {10342826, 3098505, 2119311, 193222, 25702612, 12233820, 23697382, 15056736, -21016438, -8202000}, + {-33150110, 3261608, 22745853, 7948688, 19370557, -15177665, -26171976, 6482814, -10300080, -11060101}}, + {{32869458, -5408545, 25609743, 15678670, -10687769, -15471071, 26112421, 2521008, -22664288, 6904815}, + {29506923, 4457497, 3377935, -9796444, -30510046, 12935080, 1561737, 3841096, -29003639, -6657642}, + {10340844, -6630377, -18656632, -2278430, 12621151, -13339055, 30878497, -11824370, -25584551, 5181966}}, + {{25940115, -12658025, 17324188, -10307374, -8671468, 15029094, 24396252, -16450922, -2322852, -12388574}, + {-21765684, 9916823, -1300409, 4079498, -1028346, 11909559, 1782390, 12641087, 20603771, -6561742}, + {-18882287, -11673380, 24849422, 11501709, 13161720, -4768874, 1925523, 11914390, 4662781, 7820689}}, + {{12241050, -425982, 8132691, 9393934, 32846760, -1599620, 29749456, 12172924, 16136752, 15264020}, + {-10349955, -14680563, -8211979, 2330220, -17662549, -14545780, 10658213, 6671822, 19012087, 3772772}, + {3753511, -3421066, 10617074, 2028709, 14841030, -6721664, 28718732, -15762884, 20527771, 12988982}}, + {{-14822485, -5797269, -3707987, 12689773, -898983, -10914866, -24183046, -10564943, 3299665, -12424953}, + {-16777703, -15253301, -9642417, 4978983, 3308785, 8755439, 6943197, 6461331, -25583147, 8991218}, + {-17226263, 1816362, -1673288, -6086439, 31783888, -8175991, -32948145, 7417950, -30242287, 1507265}}, + {{29692663, 6829891, -10498800, 4334896, 20945975, -11906496, -28887608, 8209391, 14606362, -10647073}, + {-3481570, 8707081, 32188102, 5672294, 22096700, 1711240, -33020695, 9761487, 4170404, -2085325}, + {-11587470, 14855945, -4127778, -1531857, -26649089, 15084046, 22186522, 16002000, -14276837, -8400798}}, + {{-4811456, 13761029, -31703877, -2483919, -3312471, 7869047, -7113572, -9620092, 13240845, 10965870}, + {-7742563, -8256762, -14768334, -13656260, -23232383, 12387166, 4498947, 14147411, 29514390, 4302863}, + {-13413405, -12407859, 20757302, -13801832, 14785143, 8976368, -5061276, -2144373, 17846988, -13971927}} + }, { + {{-2244452, -754728, -4597030, -1066309, -6247172, 1455299, -21647728, -9214789, -5222701, 12650267}, + {-9906797, -16070310, 21134160, 12198166, -27064575, 708126, 387813, 13770293, -19134326, 10958663}, + {22470984, 12369526, 23446014, -5441109, -21520802, -9698723, -11772496, -11574455, -25083830, 4271862}}, + {{-25169565, -10053642, -19909332, 15361595, -5984358, 2159192, 75375, -4278529, -32526221, 8469673}, + {15854970, 4148314, -8893890, 7259002, 11666551, 13824734, -30531198, 2697372, 24154791, -9460943}, + {15446137, -15806644, 29759747, 14019369, 30811221, -9610191, -31582008, 12840104, 24913809, 9815020}}, + {{-4709286, -5614269, -31841498, -12288893, -14443537, 10799414, -9103676, 13438769, 18735128, 9466238}, + {11933045, 9281483, 5081055, -5183824, -2628162, -4905629, -7727821, -10896103, -22728655, 16199064}, + {14576810, 379472, -26786533, -8317236, -29426508, -10812974, -102766, 1876699, 30801119, 2164795}}, + {{15995086, 3199873, 13672555, 13712240, -19378835, -4647646, -13081610, -15496269, -13492807, 1268052}, + {-10290614, -3659039, -3286592, 10948818, 23037027, 3794475, -3470338, -12600221, -17055369, 3565904}, + {29210088, -9419337, -5919792, -4952785, 10834811, -13327726, -16512102, -10820713, -27162222, -14030531}}, + {{-13161890, 15508588, 16663704, -8156150, -28349942, 9019123, -29183421, -3769423, 2244111, -14001979}, + {-5152875, -3800936, -9306475, -6071583, 16243069, 14684434, -25673088, -16180800, 13491506, 4641841}, + {10813417, 643330, -19188515, -728916, 30292062, -16600078, 27548447, -7721242, 14476989, -12767431}}, + {{10292079, 9984945, 6481436, 8279905, -7251514, 7032743, 27282937, -1644259, -27912810, 12651324}, + {-31185513, -813383, 22271204, 11835308, 10201545, 15351028, 17099662, 3988035, 21721536, -3148940}, + {10202177, -6545839, -31373232, -9574638, -32150642, -8119683, -12906320, 3852694, 13216206, 14842320}}, + {{-15815640, -10601066, -6538952, -7258995, -6984659, -6581778, -31500847, 13765824, -27434397, 9900184}, + {14465505, -13833331, -32133984, -14738873, -27443187, 12990492, 33046193, 15796406, -7051866, -8040114}, + {30924417, -8279620, 6359016, -12816335, 16508377, 9071735, -25488601, 15413635, 9524356, -7018878}}, + {{12274201, -13175547, 32627641, -1785326, 6736625, 13267305, 5237659, -5109483, 15663516, 4035784}, + {-2951309, 8903985, 17349946, 601635, -16432815, -4612556, -13732739, -15889334, -22258478, 4659091}, + {-16916263, -4952973, -30393711, -15158821, 20774812, 15897498, 5736189, 15026997, -2178256, -13455585}} + }, { + {{-8858980, -2219056, 28571666, -10155518, -474467, -10105698, -3801496, 278095, 23440562, -290208}, + {10226241, -5928702, 15139956, 120818, -14867693, 5218603, 32937275, 11551483, -16571960, -7442864}, + {17932739, -12437276, -24039557, 10749060, 11316803, 7535897, 22503767, 5561594, -3646624, 3898661}}, + {{7749907, -969567, -16339731, -16464, -25018111, 15122143, -1573531, 7152530, 21831162, 1245233}, + {26958459, -14658026, 4314586, 8346991, -5677764, 11960072, -32589295, -620035, -30402091, -16716212}, + {-12165896, 9166947, 33491384, 13673479, 29787085, 13096535, 6280834, 14587357, -22338025, 13987525}}, + {{-24349909, 7778775, 21116000, 15572597, -4833266, -5357778, -4300898, -5124639, -7469781, -2858068}, + {9681908, -6737123, -31951644, 13591838, -6883821, 386950, 31622781, 6439245, -14581012, 4091397}, + {-8426427, 1470727, -28109679, -1596990, 3978627, -5123623, -19622683, 12092163, 29077877, -14741988}}, + {{5269168, -6859726, -13230211, -8020715, 25932563, 1763552, -5606110, -5505881, -20017847, 2357889}, + {32264008, -15407652, -5387735, -1160093, -2091322, -3946900, 23104804, -12869908, 5727338, 189038}, + {14609123, -8954470, -6000566, -16622781, -14577387, -7743898, -26745169, 10942115, -25888931, -14884697}}, + {{20513500, 5557931, -15604613, 7829531, 26413943, -2019404, -21378968, 7471781, 13913677, -5137875}, + {-25574376, 11967826, 29233242, 12948236, -6754465, 4713227, -8940970, 14059180, 12878652, 8511905}, + {-25656801, 3393631, -2955415, -7075526, -2250709, 9366908, -30223418, 6812974, 5568676, -3127656}}, + {{11630004, 12144454, 2116339, 13606037, 27378885, 15676917, -17408753, -13504373, -14395196, 8070818}, + {27117696, -10007378, -31282771, -5570088, 1127282, 12772488, -29845906, 10483306, -11552749, -1028714}, + {10637467, -5688064, 5674781, 1072708, -26343588, -6982302, -1683975, 9177853, -27493162, 15431203}}, + {{20525145, 10892566, -12742472, 12779443, -29493034, 16150075, -28240519, 14943142, -15056790, -7935931}, + {-30024462, 5626926, -551567, -9981087, 753598, 11981191, 25244767, -3239766, -3356550, 9594024}, + {-23752644, 2636870, -5163910, -10103818, 585134, 7877383, 11345683, -6492290, 13352335, -10977084}}, + {{-1931799, -5407458, 3304649, -12884869, 17015806, -4877091, -29783850, -7752482, -13215537, -319204}, + {20239939, 6607058, 6203985, 3483793, -18386976, -779229, -20723742, 15077870, -22750759, 14523817}, + {27406042, -6041657, 27423596, -4497394, 4996214, 10002360, -28842031, -4545494, -30172742, -4805667}} + }, { + {{11374242, 12660715, 17861383, -12540833, 10935568, 1099227, -13886076, -9091740, -27727044, 11358504}, + {-12730809, 10311867, 1510375, 10778093, -2119455, -9145702, 32676003, 11149336, -26123651, 4985768}, + {-19096303, 341147, -6197485, -239033, 15756973, -8796662, -983043, 13794114, -19414307, -15621255}}, + {{6490081, 11940286, 25495923, -7726360, 8668373, -8751316, 3367603, 6970005, -1691065, -9004790}, + {1656497, 13457317, 15370807, 6364910, 13605745, 8362338, -19174622, -5475723, -16796596, -5031438}, + {-22273315, -13524424, -64685, -4334223, -18605636, -10921968, -20571065, -7007978, -99853, -10237333}}, + {{17747465, 10039260, 19368299, -4050591, -20630635, -16041286, 31992683, -15857976, -29260363, -5511971}, + {31932027, -4986141, -19612382, 16366580, 22023614, 88450, 11371999, -3744247, 4882242, -10626905}, + {29796507, 37186, 19818052, 10115756, -11829032, 3352736, 18551198, 3272828, -5190932, -4162409}}, + {{12501286, 4044383, -8612957, -13392385, -32430052, 5136599, -19230378, -3529697, 330070, -3659409}, + {6384877, 2899513, 17807477, 7663917, -2358888, 12363165, 25366522, -8573892, -271295, 12071499}, + {-8365515, -4042521, 25133448, -4517355, -6211027, 2265927, -32769618, 1936675, -5159697, 3829363}}, + {{28425966, -5835433, -577090, -4697198, -14217555, 6870930, 7921550, -6567787, 26333140, 14267664}, + {-11067219, 11871231, 27385719, -10559544, -4585914, -11189312, 10004786, -8709488, -21761224, 8930324}, + {-21197785, -16396035, 25654216, -1725397, 12282012, 11008919, 1541940, 4757911, -26491501, -16408940}}, + {{13537262, -7759490, -20604840, 10961927, -5922820, -13218065, -13156584, 6217254, -15943699, 13814990}, + {-17422573, 15157790, 18705543, 29619, 24409717, -260476, 27361681, 9257833, -1956526, -1776914}, + {-25045300, -10191966, 15366585, 15166509, -13105086, 8423556, -29171540, 12361135, -18685978, 4578290}}, + {{24579768, 3711570, 1342322, -11180126, -27005135, 14124956, -22544529, 14074919, 21964432, 8235257}, + {-6528613, -2411497, 9442966, -5925588, 12025640, -1487420, -2981514, -1669206, 13006806, 2355433}, + {-16304899, -13605259, -6632427, -5142349, 16974359, -10911083, 27202044, 1719366, 1141648, -12796236}}, + {{-12863944, -13219986, -8318266, -11018091, -6810145, -4843894, 13475066, -3133972, 32674895, 13715045}, + {11423335, -5468059, 32344216, 8962751, 24989809, 9241752, -13265253, 16086212, -28740881, -15642093}, + {-1409668, 12530728, -6368726, 10847387, 19531186, -14132160, -11709148, 7791794, -27245943, 4383347}} + }, { + {{-28970898, 5271447, -1266009, -9736989, -12455236, 16732599, -4862407, -4906449, 27193557, 6245191}, + {-15193956, 5362278, -1783893, 2695834, 4960227, 12840725, 23061898, 3260492, 22510453, 8577507}, + {-12632451, 11257346, -32692994, 13548177, -721004, 10879011, 31168030, 13952092, -29571492, -3635906}}, + {{3877321, -9572739, 32416692, 5405324, -11004407, -13656635, 3759769, 11935320, 5611860, 8164018}, + {-16275802, 14667797, 15906460, 12155291, -22111149, -9039718, 32003002, -8832289, 5773085, -8422109}, + {-23788118, -8254300, 1950875, 8937633, 18686727, 16459170, -905725, 12376320, 31632953, 190926}}, + {{-24593607, -16138885, -8423991, 13378746, 14162407, 6901328, -8288749, 4508564, -25341555, -3627528}, + {8884438, -5884009, 6023974, 10104341, -6881569, -4941533, 18722941, -14786005, -1672488, 827625}, + {-32720583, -16289296, -32503547, 7101210, 13354605, 2659080, -1800575, -14108036, -24878478, 1541286}}, + {{2901347, -1117687, 3880376, -10059388, -17620940, -3612781, -21802117, -3567481, 20456845, -1885033}, + {27019610, 12299467, -13658288, -1603234, -12861660, -4861471, -19540150, -5016058, 29439641, 15138866}, + {21536104, -6626420, -32447818, -10690208, -22408077, 5175814, -5420040, -16361163, 7779328, 109896}}, + {{30279744, 14648750, -8044871, 6425558, 13639621, -743509, 28698390, 12180118, 23177719, -554075}, + {26572847, 3405927, -31701700, 12890905, -19265668, 5335866, -6493768, 2378492, 4439158, -13279347}, + {-22716706, 3489070, -9225266, -332753, 18875722, -1140095, 14819434, -12731527, -17717757, -5461437}}, + {{-5056483, 16566551, 15953661, 3767752, -10436499, 15627060, -820954, 2177225, 8550082, -15114165}, + {-18473302, 16596775, -381660, 15663611, 22860960, 15585581, -27844109, -3582739, -23260460, -8428588}, + {-32480551, 15707275, -8205912, -5652081, 29464558, 2713815, -22725137, 15860482, -21902570, 1494193}}, + {{-19562091, -14087393, -25583872, -9299552, 13127842, 759709, 21923482, 16529112, 8742704, 12967017}, + {-28464899, 1553205, 32536856, -10473729, -24691605, -406174, -8914625, -2933896, -29903758, 15553883}, + {21877909, 3230008, 9881174, 10539357, -4797115, 2841332, 11543572, 14513274, 19375923, -12647961}}, + {{8832269, -14495485, 13253511, 5137575, 5037871, 4078777, 24880818, -6222716, 2862653, 9455043}, + {29306751, 5123106, 20245049, -14149889, 9592566, 8447059, -2077124, -2990080, 15511449, 4789663}, + {-20679756, 7004547, 8824831, -9434977, -4045704, -3750736, -5754762, 108893, 23513200, 16652362}} + }, { + {{-33256173, 4144782, -4476029, -6579123, 10770039, -7155542, -6650416, -12936300, -18319198, 10212860}, + {2756081, 8598110, 7383731, -6859892, 22312759, -1105012, 21179801, 2600940, -9988298, -12506466}, + {-24645692, 13317462, -30449259, -15653928, 21365574, -10869657, 11344424, 864440, -2499677, -16710063}}, + {{-26432803, 6148329, -17184412, -14474154, 18782929, -275997, -22561534, 211300, 2719757, 4940997}, + {-1323882, 3911313, -6948744, 14759765, -30027150, 7851207, 21690126, 8518463, 26699843, 5276295}, + {-13149873, -6429067, 9396249, 365013, 24703301, -10488939, 1321586, 149635, -15452774, 7159369}}, + {{9987780, -3404759, 17507962, 9505530, 9731535, -2165514, 22356009, 8312176, 22477218, -8403385}, + {18155857, -16504990, 19744716, 9006923, 15154154, -10538976, 24256460, -4864995, -22548173, 9334109}, + {2986088, -4911893, 10776628, -3473844, 10620590, -7083203, -21413845, 14253545, -22587149, 536906}}, + {{4377756, 8115836, 24567078, 15495314, 11625074, 13064599, 7390551, 10589625, 10838060, -15420424}, + {-19342404, 867880, 9277171, -3218459, -14431572, -1986443, 19295826, -15796950, 6378260, 699185}, + {7895026, 4057113, -7081772, -13077756, -17886831, -323126, -716039, 15693155, -5045064, -13373962}}, + {{-7737563, -5869402, -14566319, -7406919, 11385654, 13201616, 31730678, -10962840, -3918636, -9669325}, + {10188286, -15770834, -7336361, 13427543, 22223443, 14896287, 30743455, 7116568, -21786507, 5427593}, + {696102, 13206899, 27047647, -10632082, 15285305, -9853179, 10798490, -4578720, 19236243, 12477404}}, + {{-11229439, 11243796, -17054270, -8040865, -788228, -8167967, -3897669, 11180504, -23169516, 7733644}, + {17800790, -14036179, -27000429, -11766671, 23887827, 3149671, 23466177, -10538171, 10322027, 15313801}, + {26246234, 11968874, 32263343, -5468728, 6830755, -13323031, -15794704, -101982, -24449242, 10890804}}, + {{-31365647, 10271363, -12660625, -6267268, 16690207, -13062544, -14982212, 16484931, 25180797, -5334884}, + {-586574, 10376444, -32586414, -11286356, 19801893, 10997610, 2276632, 9482883, 316878, 13820577}, + {-9882808, -4510367, -2115506, 16457136, -11100081, 11674996, 30756178, -7515054, 30696930, -3712849}}, + {{32988917, -9603412, 12499366, 7910787, -10617257, -11931514, -7342816, -9985397, -32349517, 7392473}, + {-8855661, 15927861, 9866406, -3649411, -2396914, -16655781, -30409476, -9134995, 25112947, -2926644}, + {-2504044, -436966, 25621774, -5678772, 15085042, -5479877, -24884878, -13526194, 5537438, -13914319}} + }, { + {{-11225584, 2320285, -9584280, 10149187, -33444663, 5808648, -14876251, -1729667, 31234590, 6090599}, + {-9633316, 116426, 26083934, 2897444, -6364437, -2688086, 609721, 15878753, -6970405, -9034768}, + {-27757857, 247744, -15194774, -9002551, 23288161, -10011936, -23869595, 6503646, 20650474, 1804084}}, + {{-27589786, 15456424, 8972517, 8469608, 15640622, 4439847, 3121995, -10329713, 27842616, -202328}, + {-15306973, 2839644, 22530074, 10026331, 4602058, 5048462, 28248656, 5031932, -11375082, 12714369}, + {20807691, -7270825, 29286141, 11421711, -27876523, -13868230, -21227475, 1035546, -19733229, 12796920}}, + {{12076899, -14301286, -8785001, -11848922, -25012791, 16400684, -17591495, -12899438, 3480665, -15182815}, + {-32361549, 5457597, 28548107, 7833186, 7303070, -11953545, -24363064, -15921875, -33374054, 2771025}, + {-21389266, 421932, 26597266, 6860826, 22486084, -6737172, -17137485, -4210226, -24552282, 15673397}}, + {{-20184622, 2338216, 19788685, -9620956, -4001265, -8740893, -20271184, 4733254, 3727144, -12934448}, + {6120119, 814863, -11794402, -622716, 6812205, -15747771, 2019594, 7975683, 31123697, -10958981}, + {30069250, -11435332, 30434654, 2958439, 18399564, -976289, 12296869, 9204260, -16432438, 9648165}}, + {{32705432, -1550977, 30705658, 7451065, -11805606, 9631813, 3305266, 5248604, -26008332, -11377501}, + {17219865, 2375039, -31570947, -5575615, -19459679, 9219903, 294711, 15298639, 2662509, -16297073}, + {-1172927, -7558695, -4366770, -4287744, -21346413, -8434326, 32087529, -1222777, 32247248, -14389861}}, + {{14312628, 1221556, 17395390, -8700143, -4945741, -8684635, -28197744, -9637817, -16027623, -13378845}, + {-1428825, -9678990, -9235681, 6549687, -7383069, -468664, 23046502, 9803137, 17597934, 2346211}, + {18510800, 15337574, 26171504, 981392, -22241552, 7827556, -23491134, -11323352, 3059833, -11782870}}, + {{10141598, 6082907, 17829293, -1947643, 9830092, 13613136, -25556636, -5544586, -33502212, 3592096}, + {33114168, -15889352, -26525686, -13343397, 33076705, 8716171, 1151462, 1521897, -982665, -6837803}, + {-32939165, -4255815, 23947181, -324178, -33072974, -12305637, -16637686, 3891704, 26353178, 693168}}, + {{30374239, 1595580, -16884039, 13186931, 4600344, 406904, 9585294, -400668, 31375464, 14369965}, + {-14370654, -7772529, 1510301, 6434173, -18784789, -6262728, 32732230, -13108839, 17901441, 16011505}, + {18171223, -11934626, -12500402, 15197122, -11038147, -15230035, -19172240, -16046376, 8764035, 12309598}} + }, { + {{5975908, -5243188, -19459362, -9681747, -11541277, 14015782, -23665757, 1228319, 17544096, -10593782}, + {5811932, -1715293, 3442887, -2269310, -18367348, -8359541, -18044043, -15410127, -5565381, 12348900}, + {-31399660, 11407555, 25755363, 6891399, -3256938, 14872274, -24849353, 8141295, -10632534, -585479}}, + {{-12675304, 694026, -5076145, 13300344, 14015258, -14451394, -9698672, -11329050, 30944593, 1130208}, + {8247766, -6710942, -26562381, -7709309, -14401939, -14648910, 4652152, 2488540, 23550156, -271232}, + {17294316, -3788438, 7026748, 15626851, 22990044, 113481, 2267737, -5908146, -408818, -137719}}, + {{16091085, -16253926, 18599252, 7340678, 2137637, -1221657, -3364161, 14550936, 3260525, -7166271}, + {-4910104, -13332887, 18550887, 10864893, -16459325, -7291596, -23028869, -13204905, -12748722, 2701326}, + {-8574695, 16099415, 4629974, -16340524, -20786213, -6005432, -10018363, 9276971, 11329923, 1862132}}, + {{14763076, -15903608, -30918270, 3689867, 3511892, 10313526, -21951088, 12219231, -9037963, -940300}, + {8894987, -3446094, 6150753, 3013931, 301220, 15693451, -31981216, -2909717, -15438168, 11595570}, + {15214962, 3537601, -26238722, -14058872, 4418657, -15230761, 13947276, 10730794, -13489462, -4363670}}, + {{-2538306, 7682793, 32759013, 263109, -29984731, -7955452, -22332124, -10188635, 977108, 699994}, + {-12466472, 4195084, -9211532, 550904, -15565337, 12917920, 19118110, -439841, -30534533, -14337913}, + {31788461, -14507657, 4799989, 7372237, 8808585, -14747943, 9408237, -10051775, 12493932, -5409317}}, + {{-25680606, 5260744, -19235809, -6284470, -3695942, 16566087, 27218280, 2607121, 29375955, 6024730}, + {842132, -2794693, -4763381, -8722815, 26332018, -12405641, 11831880, 6985184, -9940361, 2854096}, + {-4847262, -7969331, 2516242, -5847713, 9695691, -7221186, 16512645, 960770, 12121869, 16648078}}, + {{-15218652, 14667096, -13336229, 2013717, 30598287, -464137, -31504922, -7882064, 20237806, 2838411}, + {-19288047, 4453152, 15298546, -16178388, 22115043, -15972604, 12544294, -13470457, 1068881, -12499905}, + {-9558883, -16518835, 33238498, 13506958, 30505848, -1114596, -8486907, -2630053, 12521378, 4845654}}, + {{-28198521, 10744108, -2958380, 10199664, 7759311, -13088600, 3409348, -873400, -6482306, -12885870}, + {-23561822, 6230156, -20382013, 10655314, -24040585, -11621172, 10477734, -1240216, -3113227, 13974498}, + {12966261, 15550616, -32038948, -1615346, 21025980, -629444, 5642325, 7188737, 18895762, 12629579}} + }, { + {{14741879, -14946887, 22177208, -11721237, 1279741, 8058600, 11758140, 789443, 32195181, 3895677}, + {10758205, 15755439, -4509950, 9243698, -4879422, 6879879, -2204575, -3566119, -8982069, 4429647}, + {-2453894, 15725973, -20436342, -10410672, -5803908, -11040220, -7135870, -11642895, 18047436, -15281743}}, + {{-25173001, -11307165, 29759956, 11776784, -22262383, -15820455, 10993114, -12850837, -17620701, -9408468}, + {21987233, 700364, -24505048, 14972008, -7774265, -5718395, 32155026, 2581431, -29958985, 8773375}, + {-25568350, 454463, -13211935, 16126715, 25240068, 8594567, 20656846, 12017935, -7874389, -13920155}}, + {{6028182, 6263078, -31011806, -11301710, -818919, 2461772, -31841174, -5468042, -1721788, -2776725}, + {-12278994, 16624277, 987579, -5922598, 32908203, 1248608, 7719845, -4166698, 28408820, 6816612}, + {-10358094, -8237829, 19549651, -12169222, 22082623, 16147817, 20613181, 13982702, -10339570, 5067943}}, + {{-30505967, -3821767, 12074681, 13582412, -19877972, 2443951, -19719286, 12746132, 5331210, -10105944}, + {30528811, 3601899, -1957090, 4619785, -27361822, -15436388, 24180793, -12570394, 27679908, -1648928}, + {9402404, -13957065, 32834043, 10838634, -26580150, -13237195, 26653274, -8685565, 22611444, -12715406}}, + {{22190590, 1118029, 22736441, 15130463, -30460692, -5991321, 19189625, -4648942, 4854859, 6622139}, + {-8310738, -2953450, -8262579, -3388049, -10401731, -271929, 13424426, -3567227, 26404409, 13001963}, + {-31241838, -15415700, -2994250, 8939346, 11562230, -12840670, -26064365, -11621720, -15405155, 11020693}}, + {{1866042, -7949489, -7898649, -10301010, 12483315, 13477547, 3175636, -12424163, 28761762, 1406734}, + {-448555, -1777666, 13018551, 3194501, -9580420, -11161737, 24760585, -4347088, 25577411, -13378680}, + {-24290378, 4759345, -690653, -1852816, 2066747, 10693769, -29595790, 9884936, -9368926, 4745410}}, + {{-9141284, 6049714, -19531061, -4341411, -31260798, 9944276, -15462008, -11311852, 10931924, -11931931}, + {-16561513, 14112680, -8012645, 4817318, -8040464, -11414606, -22853429, 10856641, -20470770, 13434654}, + {22759489, -10073434, -16766264, -1871422, 13637442, -10168091, 1765144, -12654326, 28445307, -5364710}}, + {{29875063, 12493613, 2795536, -3786330, 1710620, 15181182, -10195717, -8788675, 9074234, 1167180}, + {-26205683, 11014233, -9842651, -2635485, -26908120, 7532294, -18716888, -9535498, 3843903, 9367684}, + {-10969595, -6403711, 9591134, 9582310, 11349256, 108879, 16235123, 8601684, -139197, 4242895}} + }, { + {{22092954, -13191123, -2042793, -11968512, 32186753, -11517388, -6574341, 2470660, -27417366, 16625501}, + {-11057722, 3042016, 13770083, -9257922, 584236, -544855, -7770857, 2602725, -27351616, 14247413}, + {6314175, -10264892, -32772502, 15957557, -10157730, 168750, -8618807, 14290061, 27108877, -1180880}}, + {{-8586597, -7170966, 13241782, 10960156, -32991015, -13794596, 33547976, -11058889, -27148451, 981874}, + {22833440, 9293594, -32649448, -13618667, -9136966, 14756819, -22928859, -13970780, -10479804, -16197962}, + {-7768587, 3326786, -28111797, 10783824, 19178761, 14905060, 22680049, 13906969, -15933690, 3797899}}, + {{21721356, -4212746, -12206123, 9310182, -3882239, -13653110, 23740224, -2709232, 20491983, -8042152}, + {9209270, -15135055, -13256557, -6167798, -731016, 15289673, 25947805, 15286587, 30997318, -6703063}, + {7392032, 16618386, 23946583, -8039892, -13265164, -1533858, -14197445, -2321576, 17649998, -250080}}, + {{-9301088, -14193827, 30609526, -3049543, -25175069, -1283752, -15241566, -9525724, -2233253, 7662146}, + {-17558673, 1763594, -33114336, 15908610, -30040870, -12174295, 7335080, -8472199, -3174674, 3440183}, + {-19889700, -5977008, -24111293, -9688870, 10799743, -16571957, 40450, -4431835, 4862400, 1133}}, + {{-32856209, -7873957, -5422389, 14860950, -16319031, 7956142, 7258061, 311861, -30594991, -7379421}, + {-3773428, -1565936, 28985340, 7499440, 24445838, 9325937, 29727763, 16527196, 18278453, 15405622}, + {-4381906, 8508652, -19898366, -3674424, -5984453, 15149970, -13313598, 843523, -21875062, 13626197}}, + {{2281448, -13487055, -10915418, -2609910, 1879358, 16164207, -10783882, 3953792, 13340839, 15928663}, + {31727126, -7179855, -18437503, -8283652, 2875793, -16390330, -25269894, -7014826, -23452306, 5964753}, + {4100420, -5959452, -17179337, 6017714, -18705837, 12227141, -26684835, 11344144, 2538215, -7570755}}, + {{-9433605, 6123113, 11159803, -2156608, 30016280, 14966241, -20474983, 1485421, -629256, -15958862}, + {-26804558, 4260919, 11851389, 9658551, -32017107, 16367492, -20205425, -13191288, 11659922, -11115118}, + {26180396, 10015009, -30844224, -8581293, 5418197, 9480663, 2231568, -10170080, 33100372, -1306171}}, + {{15121113, -5201871, -10389905, 15427821, -27509937, -15992507, 21670947, 4486675, -5931810, -14466380}, + {16166486, -9483733, -11104130, 6023908, -31926798, -1364923, 2340060, -16254968, -10735770, -10039824}, + {28042865, -3557089, -12126526, 12259706, -3717498, -6945899, 6766453, -8689599, 18036436, 5803270}} + }, { + {{-817581, 6763912, 11803561, 1585585, 10958447, -2671165, 23855391, 4598332, -6159431, -14117438}, + {-31031306, -14256194, 17332029, -2383520, 31312682, -5967183, 696309, 50292, -20095739, 11763584}, + {-594563, -2514283, -32234153, 12643980, 12650761, 14811489, 665117, -12613632, -19773211, -10713562}}, + {{30464590, -11262872, -4127476, -12734478, 19835327, -7105613, -24396175, 2075773, -17020157, 992471}, + {18357185, -6994433, 7766382, 16342475, -29324918, 411174, 14578841, 8080033, -11574335, -10601610}, + {19598397, 10334610, 12555054, 2555664, 18821899, -10339780, 21873263, 16014234, 26224780, 16452269}}, + {{-30223925, 5145196, 5944548, 16385966, 3976735, 2009897, -11377804, -7618186, -20533829, 3698650}, + {14187449, 3448569, -10636236, -10810935, -22663880, -3433596, 7268410, -10890444, 27394301, 12015369}, + {19695761, 16087646, 28032085, 12999827, 6817792, 11427614, 20244189, -1312777, -13259127, -3402461}}, + {{30860103, 12735208, -1888245, -4699734, -16974906, 2256940, -8166013, 12298312, -8550524, -10393462}, + {-5719826, -11245325, -1910649, 15569035, 26642876, -7587760, -5789354, -15118654, -4976164, 12651793}, + {-2848395, 9953421, 11531313, -5282879, 26895123, -12697089, -13118820, -16517902, 9768698, -2533218}}, + {{-24719459, 1894651, -287698, -4704085, 15348719, -8156530, 32767513, 12765450, 4940095, 10678226}, + {18860224, 15980149, -18987240, -1562570, -26233012, -11071856, -7843882, 13944024, -24372348, 16582019}, + {-15504260, 4970268, -29893044, 4175593, -20993212, -2199756, -11704054, 15444560, -11003761, 7989037}}, + {{31490452, 5568061, -2412803, 2182383, -32336847, 4531686, -32078269, 6200206, -19686113, -14800171}, + {-17308668, -15879940, -31522777, -2831, -32887382, 16375549, 8680158, -16371713, 28550068, -6857132}, + {-28126887, -5688091, 16837845, -1820458, -6850681, 12700016, -30039981, 4364038, 1155602, 5988841}}, + {{21890435, -13272907, -12624011, 12154349, -7831873, 15300496, 23148983, -4470481, 24618407, 8283181}, + {-33136107, -10512751, 9975416, 6841041, -31559793, 16356536, 3070187, -7025928, 1466169, 10740210}, + {-1509399, -15488185, -13503385, -10655916, 32799044, 909394, -13938903, -5779719, -32164649, -15327040}}, + {{3960823, -14267803, -28026090, -15918051, -19404858, 13146868, 15567327, 951507, -3260321, -573935}, + {24740841, 5052253, -30094131, 8961361, 25877428, 6165135, -24368180, 14397372, -7380369, -6144105}, + {-28888365, 3510803, -28103278, -1158478, -11238128, -10631454, -15441463, -14453128, -1625486, -6494814}} + }, { + {{793299, -9230478, 8836302, -6235707, -27360908, -2369593, 33152843, -4885251, -9906200, -621852}, + {5666233, 525582, 20782575, -8038419, -24538499, 14657740, 16099374, 1468826, -6171428, -15186581}, + {-4859255, -3779343, -2917758, -6748019, 7778750, 11688288, -30404353, -9871238, -1558923, -9863646}}, + {{10896332, -7719704, 824275, 472601, -19460308, 3009587, 25248958, 14783338, -30581476, -15757844}, + {10566929, 12612572, -31944212, 11118703, -12633376, 12362879, 21752402, 8822496, 24003793, 14264025}, + {27713862, -7355973, -11008240, 9227530, 27050101, 2504721, 23886875, -13117525, 13958495, -5732453}}, + {{-23481610, 4867226, -27247128, 3900521, 29838369, -8212291, -31889399, -10041781, 7340521, -15410068}, + {4646514, -8011124, -22766023, -11532654, 23184553, 8566613, 31366726, -1381061, -15066784, -10375192}, + {-17270517, 12723032, -16993061, 14878794, 21619651, -6197576, 27584817, 3093888, -8843694, 3849921}}, + {{-9064912, 2103172, 25561640, -15125738, -5239824, 9582958, 32477045, -9017955, 5002294, -15550259}, + {-12057553, -11177906, 21115585, -13365155, 8808712, -12030708, 16489530, 13378448, -25845716, 12741426}, + {-5946367, 10645103, -30911586, 15390284, -3286982, -7118677, 24306472, 15852464, 28834118, -7646072}}, + {{-17335748, -9107057, -24531279, 9434953, -8472084, -583362, -13090771, 455841, 20461858, 5491305}, + {13669248, -16095482, -12481974, -10203039, -14569770, -11893198, -24995986, 11293807, -28588204, -9421832}, + {28497928, 6272777, -33022994, 14470570, 8906179, -1225630, 18504674, -14165166, 29867745, -8795943}}, + {{-16207023, 13517196, -27799630, -13697798, 24009064, -6373891, -6367600, -13175392, 22853429, -4012011}, + {24191378, 16712145, -13931797, 15217831, 14542237, 1646131, 18603514, -11037887, 12876623, -2112447}, + {17902668, 4518229, -411702, -2829247, 26878217, 5258055, -12860753, 608397, 16031844, 3723494}}, + {{-28632773, 12763728, -20446446, 7577504, 33001348, -13017745, 17558842, -7872890, 23896954, -4314245}, + {-20005381, -12011952, 31520464, 605201, 2543521, 5991821, -2945064, 7229064, -9919646, -8826859}, + {28816045, 298879, -28165016, -15920938, 19000928, -1665890, -12680833, -2949325, -18051778, -2082915}}, + {{16000882, -344896, 3493092, -11447198, -29504595, -13159789, 12577740, 16041268, -19715240, 7847707}, + {10151868, 10572098, 27312476, 7922682, 14825339, 4723128, -32855931, -6519018, -10020567, 3852848}, + {-11430470, 15697596, -21121557, -4420647, 5386314, 15063598, 16514493, -15932110, 29330899, -15076224}} + }, { + {{-25499735, -4378794, -15222908, -6901211, 16615731, 2051784, 3303702, 15490, -27548796, 12314391}, + {15683520, -6003043, 18109120, -9980648, 15337968, -5997823, -16717435, 15921866, 16103996, -3731215}, + {-23169824, -10781249, 13588192, -1628807, -3798557, -1074929, -19273607, 5402699, -29815713, -9841101}}, + {{23190676, 2384583, -32714340, 3462154, -29903655, -1529132, -11266856, 8911517, -25205859, 2739713}, + {21374101, -3554250, -33524649, 9874411, 15377179, 11831242, -33529904, 6134907, 4931255, 11987849}, + {-7732, -2978858, -16223486, 7277597, 105524, -322051, -31480539, 13861388, -30076310, 10117930}}, + {{-29501170, -10744872, -26163768, 13051539, -25625564, 5089643, -6325503, 6704079, 12890019, 15728940}, + {-21972360, -11771379, -951059, -4418840, 14704840, 2695116, 903376, -10428139, 12885167, 8311031}, + {-17516482, 5352194, 10384213, -13811658, 7506451, 13453191, 26423267, 4384730, 1888765, -5435404}}, + {{-25817338, -3107312, -13494599, -3182506, 30896459, -13921729, -32251644, -12707869, -19464434, -3340243}, + {-23607977, -2665774, -526091, 4651136, 5765089, 4618330, 6092245, 14845197, 17151279, -9854116}, + {-24830458, -12733720, -15165978, 10367250, -29530908, -265356, 22825805, -7087279, -16866484, 16176525}}, + {{-23583256, 6564961, 20063689, 3798228, -4740178, 7359225, 2006182, -10363426, -28746253, -10197509}, + {-10626600, -4486402, -13320562, -5125317, 3432136, -6393229, 23632037, -1940610, 32808310, 1099883}, + {15030977, 5768825, -27451236, -2887299, -6427378, -15361371, -15277896, -6809350, 2051441, -15225865}}, + {{-3362323, -7239372, 7517890, 9824992, 23555850, 295369, 5148398, -14154188, -22686354, 16633660}, + {4577086, -16752288, 13249841, -15304328, 19958763, -14537274, 18559670, -10759549, 8402478, -9864273}, + {-28406330, -1051581, -26790155, -907698, -17212414, -11030789, 9453451, -14980072, 17983010, 9967138}}, + {{-25762494, 6524722, 26585488, 9969270, 24709298, 1220360, -1677990, 7806337, 17507396, 3651560}, + {-10420457, -4118111, 14584639, 15971087, -15768321, 8861010, 26556809, -5574557, -18553322, -11357135}, + {2839101, 14284142, 4029895, 3472686, 14402957, 12689363, -26642121, 8459447, -5605463, -7621941}}, + {{-4839289, -3535444, 9744961, 2871048, 25113978, 3187018, -25110813, -849066, 17258084, -7977739}, + {18164541, -10595176, -17154882, -1542417, 19237078, -9745295, 23357533, -15217008, 26908270, 12150756}, + {-30264870, -7647865, 5112249, -7036672, -1499807, -6974257, 43168, -5537701, -32302074, 16215819}} + }, { + {{-6898905, 9824394, -12304779, -4401089, -31397141, -6276835, 32574489, 12532905, -7503072, -8675347}, + {-27343522, -16515468, -27151524, -10722951, 946346, 16291093, 254968, 7168080, 21676107, -1943028}, + {21260961, -8424752, -16831886, -11920822, -23677961, 3968121, -3651949, -6215466, -3556191, -7913075}}, + {{16544754, 13250366, -16804428, 15546242, -4583003, 12757258, -2462308, -8680336, -18907032, -9662799}, + {-2415239, -15577728, 18312303, 4964443, -15272530, -12653564, 26820651, 16690659, 25459437, -4564609}, + {-25144690, 11425020, 28423002, -11020557, -6144921, -15826224, 9142795, -2391602, -6432418, -1644817}}, + {{-23104652, 6253476, 16964147, -3768872, -25113972, -12296437, -27457225, -16344658, 6335692, 7249989}, + {-30333227, 13979675, 7503222, -12368314, -11956721, -4621693, -30272269, 2682242, 25993170, -12478523}, + {4364628, 5930691, 32304656, -10044554, -8054781, 15091131, 22857016, -10598955, 31820368, 15075278}}, + {{31879134, -8918693, 17258761, 90626, -8041836, -4917709, 24162788, -9650886, -17970238, 12833045}, + {19073683, 14851414, -24403169, -11860168, 7625278, 11091125, -19619190, 2074449, -9413939, 14905377}, + {24483667, -11935567, -2518866, -11547418, -1553130, 15355506, -25282080, 9253129, 27628530, -7555480}}, + {{17597607, 8340603, 19355617, 552187, 26198470, -3176583, 4593324, -9157582, -14110875, 15297016}, + {510886, 14337390, -31785257, 16638632, 6328095, 2713355, -20217417, -11864220, 8683221, 2921426}, + {18606791, 11874196, 27155355, -5281482, -24031742, 6265446, -25178240, -1278924, 4674690, 13890525}}, + {{13609624, 13069022, -27372361, -13055908, 24360586, 9592974, 14977157, 9835105, 4389687, 288396}, + {9922506, -519394, 13613107, 5883594, -18758345, -434263, -12304062, 8317628, 23388070, 16052080}, + {12720016, 11937594, -31970060, -5028689, 26900120, 8561328, -20155687, -11632979, -14754271, -10812892}}, + {{15961858, 14150409, 26716931, -665832, -22794328, 13603569, 11829573, 7467844, -28822128, 929275}, + {11038231, -11582396, -27310482, -7316562, -10498527, -16307831, -23479533, -9371869, -21393143, 2465074}, + {20017163, -4323226, 27915242, 1529148, 12396362, 15675764, 13817261, -9658066, 2463391, -4622140}}, + {{-16358878, -12663911, -12065183, 4996454, -1256422, 1073572, 9583558, 12851107, 4003896, 12673717}, + {-1731589, -15155870, -3262930, 16143082, 19294135, 13385325, 14741514, -9103726, 7903886, 2348101}, + {24536016, -16515207, 12715592, -3862155, 1511293, 10047386, -3842346, -7129159, -28377538, 10048127}} + }, { + {{-12622226, -6204820, 30718825, 2591312, -10617028, 12192840, 18873298, -7297090, -32297756, 15221632}, + {-26478122, -11103864, 11546244, -1852483, 9180880, 7656409, -21343950, 2095755, 29769758, 6593415}, + {-31994208, -2907461, 4176912, 3264766, 12538965, -868111, 26312345, -6118678, 30958054, 8292160}}, + {{31429822, -13959116, 29173532, 15632448, 12174511, -2760094, 32808831, 3977186, 26143136, -3148876}, + {22648901, 1402143, -22799984, 13746059, 7936347, 365344, -8668633, -1674433, -3758243, -2304625}, + {-15491917, 8012313, -2514730, -12702462, -23965846, -10254029, -1612713, -1535569, -16664475, 8194478}}, + {{27338066, -7507420, -7414224, 10140405, -19026427, -6589889, 27277191, 8855376, 28572286, 3005164}, + {26287124, 4821776, 25476601, -4145903, -3764513, -15788984, -18008582, 1182479, -26094821, -13079595}, + {-7171154, 3178080, 23970071, 6201893, -17195577, -4489192, -21876275, -13982627, 32208683, -1198248}}, + {{-16657702, 2817643, -10286362, 14811298, 6024667, 13349505, -27315504, -10497842, -27672585, -11539858}, + {15941029, -9405932, -21367050, 8062055, 31876073, -238629, -15278393, -1444429, 15397331, -4130193}, + {8934485, -13485467, -23286397, -13423241, -32446090, 14047986, 31170398, -1441021, -27505566, 15087184}}, + {{-18357243, -2156491, 24524913, -16677868, 15520427, -6360776, -15502406, 11461896, 16788528, -5868942}, + {-1947386, 16013773, 21750665, 3714552, -17401782, -16055433, -3770287, -10323320, 31322514, -11615635}, + {21426655, -5650218, -13648287, -5347537, -28812189, -4920970, -18275391, -14621414, 13040862, -12112948}}, + {{11293895, 12478086, -27136401, 15083750, -29307421, 14748872, 14555558, -13417103, 1613711, 4896935}, + {-25894883, 15323294, -8489791, -8057900, 25967126, -13425460, 2825960, -4897045, -23971776, -11267415}, + {-15924766, -5229880, -17443532, 6410664, 3622847, 10243618, 20615400, 12405433, -23753030, -8436416}}, + {{-7091295, 12556208, -20191352, 9025187, -17072479, 4333801, 4378436, 2432030, 23097949, -566018}, + {4565804, -16025654, 20084412, -7842817, 1724999, 189254, 24767264, 10103221, -18512313, 2424778}, + {366633, -11976806, 8173090, -6890119, 30788634, 5745705, -7168678, 1344109, -3642553, 12412659}}, + {{-24001791, 7690286, 14929416, -168257, -32210835, -13412986, 24162697, -15326504, -3141501, 11179385}, + {18289522, -14724954, 8056945, 16430056, -21729724, 7842514, -6001441, -1486897, -18684645, -11443503}, + {476239, 6601091, -6152790, -9723375, 17503545, -4863900, 27672959, 13403813, 11052904, 5219329}} + }, { + {{20678546, -8375738, -32671898, 8849123, -5009758, 14574752, 31186971, -3973730, 9014762, -8579056}, + {-13644050, -10350239, -15962508, 5075808, -1514661, -11534600, -33102500, 9160280, 8473550, -3256838}, + {24900749, 14435722, 17209120, -15292541, -22592275, 9878983, -7689309, -16335821, -24568481, 11788948}}, + {{-3118155, -11395194, -13802089, 14797441, 9652448, -6845904, -20037437, 10410733, -24568470, -1458691}, + {-15659161, 16736706, -22467150, 10215878, -9097177, 7563911, 11871841, -12505194, -18513325, 8464118}, + {-23400612, 8348507, -14585951, -861714, -3950205, -6373419, 14325289, 8628612, 33313881, -8370517}}, + {{-20186973, -4967935, 22367356, 5271547, -1097117, -4788838, -24805667, -10236854, -8940735, -5818269}, + {-6948785, -1795212, -32625683, -16021179, 32635414, -7374245, 15989197, -12838188, 28358192, -4253904}, + {-23561781, -2799059, -32351682, -1661963, -9147719, 10429267, -16637684, 4072016, -5351664, 5596589}}, + {{-28236598, -3390048, 12312896, 6213178, 3117142, 16078565, 29266239, 2557221, 1768301, 15373193}, + {-7243358, -3246960, -4593467, -7553353, -127927, -912245, -1090902, -4504991, -24660491, 3442910}, + {-30210571, 5124043, 14181784, 8197961, 18964734, -11939093, 22597931, 7176455, -18585478, 13365930}}, + {{-7877390, -1499958, 8324673, 4690079, 6261860, 890446, 24538107, -8570186, -9689599, -3031667}, + {25008904, -10771599, -4305031, -9638010, 16265036, 15721635, 683793, -11823784, 15723479, -15163481}, + {-9660625, 12374379, -27006999, -7026148, -7724114, -12314514, 11879682, 5400171, 519526, -1235876}}, + {{22258397, -16332233, -7869817, 14613016, -22520255, -2950923, -20353881, 7315967, 16648397, 7605640}, + {-8081308, -8464597, -8223311, 9719710, 19259459, -15348212, 23994942, -5281555, -9468848, 4763278}, + {-21699244, 9220969, -15730624, 1084137, -25476107, -2852390, 31088447, -7764523, -11356529, 728112}}, + {{26047220, -11751471, -6900323, -16521798, 24092068, 9158119, -4273545, -12555558, -29365436, -5498272}, + {17510331, -322857, 5854289, 8403524, 17133918, -3112612, -28111007, 12327945, 10750447, 10014012}, + {-10312768, 3936952, 9156313, -8897683, 16498692, -994647, -27481051, -666732, 3424691, 7540221}}, + {{30322361, -6964110, 11361005, -4143317, 7433304, 4989748, -7071422, -16317219, -9244265, 15258046}, + {13054562, -2779497, 19155474, 469045, -12482797, 4566042, 5631406, 2711395, 1062915, -5136345}, + {-19240248, -11254599, -29509029, -7499965, -5835763, 13005411, -6066489, 12194497, 32960380, 1459310}} + }, { + {{19852034, 7027924, 23669353, 10020366, 8586503, -6657907, 394197, -6101885, 18638003, -11174937}, + {31395534, 15098109, 26581030, 8030562, -16527914, -5007134, 9012486, -7584354, -6643087, -5442636}, + {-9192165, -2347377, -1997099, 4529534, 25766844, 607986, -13222, 9677543, -32294889, -6456008}}, + {{-2444496, -149937, 29348902, 8186665, 1873760, 12489863, -30934579, -7839692, -7852844, -8138429}, + {-15236356, -15433509, 7766470, 746860, 26346930, -10221762, -27333451, 10754588, -9431476, 5203576}, + {31834314, 14135496, -770007, 5159118, 20917671, -16768096, -7467973, -7337524, 31809243, 7347066}}, + {{-9606723, -11874240, 20414459, 13033986, 13716524, -11691881, 19797970, -12211255, 15192876, -2087490}, + {-12663563, -2181719, 1168162, -3804809, 26747877, -14138091, 10609330, 12694420, 33473243, -13382104}, + {33184999, 11180355, 15832085, -11385430, -1633671, 225884, 15089336, -11023903, -6135662, 14480053}}, + {{31308717, -5619998, 31030840, -1897099, 15674547, -6582883, 5496208, 13685227, 27595050, 8737275}, + {-20318852, -15150239, 10933843, -16178022, 8335352, -7546022, -31008351, -12610604, 26498114, 66511}, + {22644454, -8761729, -16671776, 4884562, -3105614, -13559366, 30540766, -4286747, -13327787, -7515095}}, + {{-28017847, 9834845, 18617207, -2681312, -3401956, -13307506, 8205540, 13585437, -17127465, 15115439}, + {23711543, -672915, 31206561, -8362711, 6164647, -9709987, -33535882, -1426096, 8236921, 16492939}, + {-23910559, -13515526, -26299483, -4503841, 25005590, -7687270, 19574902, 10071562, 6708380, -6222424}}, + {{2101391, -4930054, 19702731, 2367575, -15427167, 1047675, 5301017, 9328700, 29955601, -11678310}, + {3096359, 9271816, -21620864, -15521844, -14847996, -7592937, -25892142, -12635595, -9917575, 6216608}, + {-32615849, 338663, -25195611, 2510422, -29213566, -13820213, 24822830, -6146567, -26767480, 7525079}}, + {{-23066649, -13985623, 16133487, -7896178, -3389565, 778788, -910336, -2782495, -19386633, 11994101}, + {21691500, -13624626, -641331, -14367021, 3285881, -3483596, -25064666, 9718258, -7477437, 13381418}, + {18445390, -4202236, 14979846, 11622458, -1727110, -3582980, 23111648, -6375247, 28535282, 15779576}}, + {{30098053, 3089662, -9234387, 16662135, -21306940, 11308411, -14068454, 12021730, 9955285, -16303356}, + {9734894, -14576830, -7473633, -9138735, 2060392, 11313496, -18426029, 9924399, 20194861, 13380996}, + {-26378102, -7965207, -22167821, 15789297, -18055342, -6168792, -1984914, 15707771, 26342023, 10146099}} + }, { + {{-26016874, -219943, 21339191, -41388, 19745256, -2878700, -29637280, 2227040, 21612326, -545728}, + {-13077387, 1184228, 23562814, -5970442, -20351244, -6348714, 25764461, 12243797, -20856566, 11649658}, + {-10031494, 11262626, 27384172, 2271902, 26947504, -15997771, 39944, 6114064, 33514190, 2333242}}, + {{-21433588, -12421821, 8119782, 7219913, -21830522, -9016134, -6679750, -12670638, 24350578, -13450001}, + {-4116307, -11271533, -23886186, 4843615, -30088339, 690623, -31536088, -10406836, 8317860, 12352766}, + {18200138, -14475911, -33087759, -2696619, -23702521, -9102511, -23552096, -2287550, 20712163, 6719373}}, + {{26656208, 6075253, -7858556, 1886072, -28344043, 4262326, 11117530, -3763210, 26224235, -3297458}, + {-17168938, -14854097, -3395676, -16369877, -19954045, 14050420, 21728352, 9493610, 18620611, -16428628}, + {-13323321, 13325349, 11432106, 5964811, 18609221, 6062965, -5269471, -9725556, -30701573, -16479657}}, + {{-23860538, -11233159, 26961357, 1640861, -32413112, -16737940, 12248509, -5240639, 13735342, 1934062}, + {25089769, 6742589, 17081145, -13406266, 21909293, -16067981, -15136294, -3765346, -21277997, 5473616}, + {31883677, -7961101, 1083432, -11572403, 22828471, 13290673, -7125085, 12469656, 29111212, -5451014}}, + {{24244947, -15050407, -26262976, 2791540, -14997599, 16666678, 24367466, 6388839, -10295587, 452383}, + {-25640782, -3417841, 5217916, 16224624, 19987036, -4082269, -24236251, -5915248, 15766062, 8407814}, + {-20406999, 13990231, 15495425, 16395525, 5377168, 15166495, -8917023, -4388953, -8067909, 2276718}}, + {{30157918, 12924066, -17712050, 9245753, 19895028, 3368142, -23827587, 5096219, 22740376, -7303417}, + {2041139, -14256350, 7783687, 13876377, -25946985, -13352459, 24051124, 13742383, -15637599, 13295222}, + {33338237, -8505733, 12532113, 7977527, 9106186, -1715251, -17720195, -4612972, -4451357, -14669444}}, + {{-20045281, 5454097, -14346548, 6447146, 28862071, 1883651, -2469266, -4141880, 7770569, 9620597}, + {23208068, 7979712, 33071466, 8149229, 1758231, -10834995, 30945528, -1694323, -33502340, -14767970}, + {1439958, -16270480, -1079989, -793782, 4625402, 10647766, -5043801, 1220118, 30494170, -11440799}}, + {{-5037580, -13028295, -2970559, -3061767, 15640974, -6701666, -26739026, 926050, -1684339, -13333647}, + {13908495, -3549272, 30919928, -6273825, -21521863, 7989039, 9021034, 9078865, 3353509, 4033511}, + {-29663431, -15113610, 32259991, -344482, 24295849, -12912123, 23161163, 8839127, 27485041, 7356032}} + }, { + {{9661027, 705443, 11980065, -5370154, -1628543, 14661173, -6346142, 2625015, 28431036, -16771834}, + {-23839233, -8311415, -25945511, 7480958, -17681669, -8354183, -22545972, 14150565, 15970762, 4099461}, + {29262576, 16756590, 26350592, -8793563, 8529671, -11208050, 13617293, -9937143, 11465739, 8317062}}, + {{-25493081, -6962928, 32500200, -9419051, -23038724, -2302222, 14898637, 3848455, 20969334, -5157516}, + {-20384450, -14347713, -18336405, 13884722, -33039454, 2842114, -21610826, -3649888, 11177095, 14989547}, + {-24496721, -11716016, 16959896, 2278463, 12066309, 10137771, 13515641, 2581286, -28487508, 9930240}}, + {{-17751622, -2097826, 16544300, -13009300, -15914807, -14949081, 18345767, -13403753, 16291481, -5314038}, + {-33229194, 2553288, 32678213, 9875984, 8534129, 6889387, -9676774, 6957617, 4368891, 9788741}, + {16660756, 7281060, -10830758, 12911820, 20108584, -8101676, -21722536, -8613148, 16250552, -11111103}}, + {{-19765507, 2390526, -16551031, 14161980, 1905286, 6414907, 4689584, 10604807, -30190403, 4782747}, + {-1354539, 14736941, -7367442, -13292886, 7710542, -14155590, -9981571, 4383045, 22546403, 437323}, + {31665577, -12180464, -16186830, 1491339, -18368625, 3294682, 27343084, 2786261, -30633590, -14097016}}, + {{-14467279, -683715, -33374107, 7448552, 19294360, 14334329, -19690631, 2355319, -19284671, -6114373}, + {15121312, -15796162, 6377020, -6031361, -10798111, -12957845, 18952177, 15496498, -29380133, 11754228}, + {-2637277, -13483075, 8488727, -14303896, 12728761, -1622493, 7141596, 11724556, 22761615, -10134141}}, + {{16918416, 11729663, -18083579, 3022987, -31015732, -13339659, -28741185, -12227393, 32851222, 11717399}, + {11166634, 7338049, -6722523, 4531520, -29468672, -7302055, 31474879, 3483633, -1193175, -4030831}, + {-185635, 9921305, 31456609, -13536438, -12013818, 13348923, 33142652, 6546660, -19985279, -3948376}}, + {{-32460596, 11266712, -11197107, -7899103, 31703694, 3855903, -8537131, -12833048, -30772034, -15486313}, + {-18006477, 12709068, 3991746, -6479188, -21491523, -10550425, -31135347, -16049879, 10928917, 3011958}, + {-6957757, -15594337, 31696059, 334240, 29576716, 14796075, -30831056, -12805180, 18008031, 10258577}}, + {{-22448644, 15655569, 7018479, -4410003, -30314266, -1201591, -1853465, 1367120, 25127874, 6671743}, + {29701166, -14373934, -10878120, 9279288, -17568, 13127210, 21382910, 11042292, 25838796, 4642684}, + {-20430234, 14955537, -24126347, 8124619, -5369288, -5990470, 30468147, -13900640, 18423289, 4177476}} + } +}; + +const ge_precomp ge_Bi[8] = { + {{25967493, -14356035, 29566456, 3660896, -12694345, 4014787, 27544626, -11754271, -6079156, 2047605}, + {-12545711, 934262, -2722910, 3049990, -727428, 9406986, 12720692, 5043384, 19500929, -15469378}, + {-8738181, 4489570, 9688441, -14785194, 10184609, -12363380, 29287919, 11864899, -24514362, -4438546}}, {{15636291, -9688557, 24204773, -7912398, 616977, -16685262, 27787600, -14772189, 28944400, -1550024}, + {16568933, 4717097, -11556148, -1102322, 15682896, -11807043, 16354577, -11775962, 7689662, 11199574}, + {30464156, -5976125, -11779434, -15670865, 23220365, 15915852, 7512774, 10017326, -17749093, -9920357}}, {{10861363, 11473154, 27284546, 1981175, -30064349, 12577861, 32867885, 14515107, -15438304, 10819380}, + {4708026, 6336745, 20377586, 9066809, -11272109, 6594696, -25653668, 12483688, -12668491, 5581306}, + {19563160, 16186464, -29386857, 4097519, 10237984, -4348115, 28542350, 13850243, -23678021, -15815942}}, {{5153746, 9909285, 1723747, -2777874, 30523605, 5516873, 19480852, 5230134, -23952439, -15175766}, + {-30269007, -3463509, 7665486, 10083793, 28475525, 1649722, 20654025, 16520125, 30598449, 7715701}, + {28881845, 14381568, 9657904, 3680757, -20181635, 7843316, -31400660, 1370708, 29794553, -1409300}}, {{-22518993, -6692182, 14201702, -8745502, -23510406, 8844726, 18474211, -1361450, -13062696, 13821877}, + {-6455177, -7839871, 3374702, -4740862, -27098617, -10571707, 31655028, -7212327, 18853322, -14220951}, + {4566830, -12963868, -28974889, -12240689, -7602672, -2830569, -8514358, -10431137, 2207753, -3209784}}, {{-25154831, -4185821, 29681144, 7868801, -6854661, -9423865, -12437364, -663000, -31111463, -16132436}, + {25576264, -2703214, 7349804, -11814844, 16472782, 9300885, 3844789, 15725684, 171356, 6466918}, + {23103977, 13316479, 9739013, -16149481, 817875, -15038942, 8965339, -14088058, -30714912, 16193877}}, {{-33521811, 3180713, -2394130, 14003687, -16903474, -16270840, 17238398, 4729455, -18074513, 9256800}, + {-25182317, -4174131, 32336398, 5036987, -21236817, 11360617, 22616405, 9761698, -19827198, 630305}, + {-13720693, 2639453, -24237460, -7406481, 9494427, -5774029, -6554551, -15960994, -2449256, -14291300}}, {{-3151181, -5046075, 9282714, 6866145, -31907062, -863023, -18940575, 15033784, 25105118, -7894876}, + {-24326370, 15950226, -31801215, -14592823, -11662737, -5090925, 1573892, -2625887, 2198790, -15804619}, + {-3099351, 10324967, -2241613, 7453183, -5446979, -2735503, -13812022, -16236442, -32461234, -12290683}} +}; + +/* A = 2 * (1 - d) / (1 + d) = 486662 */ +const fe fe_ma2 = {-12721188, -3529, 0, 0, 0, 0, 0, 0, 0, 0}; /* -A^2 */ +const fe fe_ma = {-486662, 0, 0, 0, 0, 0, 0, 0, 0, 0}; /* -A */ +const fe fe_fffb1 = {-31702527, -2466483, -26106795, -12203692, -12169197, -321052, 14850977, -10296299, -16929438, -407568}; /* sqrt(-2 * A * (A + 2)) */ +const fe fe_fffb2 = {8166131, -6741800, -17040804, 3154616, 21461005, 1466302, -30876704, -6368709, 10503587, -13363080}; /* sqrt(2 * A * (A + 2)) */ +const fe fe_fffb3 = {-13620103, 14639558, 4532995, 7679154, 16815101, -15883539, -22863840, -14813421, 13716513, -6477756}; /* sqrt(-sqrt(-1) * A * (A + 2)) */ +const fe fe_fffb4 = {-21786234, -12173074, 21573800, 4524538, -4645904, 16204591, 8012863, -8444712, 3212926, 6885324}; /* sqrt(sqrt(-1) * A * (A + 2)) */ +const ge_p3 ge_p3_identity = { {0}, {1, 0}, {1, 0}, {0} }; +const ge_p3 ge_p3_H = { + {7329926, -15101362, 31411471, 7614783, 27996851, -3197071, -11157635, -6878293, 466949, -7986503}, + {5858699, 5096796, 21321203, -7536921, -5553480, -11439507, -5627669, 15045946, 19977121, 5275251}, + {1, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {23443568, -5110398, -8776029, -4345135, 6889568, -14710814, 7474843, 3279062, 14550766, -7453428} +}; diff --git a/chain/monero/crypto/cref/crypto-ops.c b/chain/monero/crypto/cref/crypto-ops.c new file mode 100644 index 00000000..86c42e64 --- /dev/null +++ b/chain/monero/crypto/cref/crypto-ops.c @@ -0,0 +1,3897 @@ +// Copyright (c) 2014-2024, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#include +#include + +/* warnings.h removed */ +#include "crypto-ops.h" + + + +/* Predeclarations */ + +static void fe_sq(fe, const fe); +static void ge_madd(ge_p1p1 *, const ge_p3 *, const ge_precomp *); +static void ge_msub(ge_p1p1 *, const ge_p3 *, const ge_precomp *); +static void ge_p2_0(ge_p2 *); +static void ge_p3_dbl(ge_p1p1 *, const ge_p3 *); +static void fe_divpowm1(fe, const fe, const fe); + +/* Common functions */ + +uint64_t load_3(const unsigned char *in) { + uint64_t result; + result = (uint64_t) in[0]; + result |= ((uint64_t) in[1]) << 8; + result |= ((uint64_t) in[2]) << 16; + return result; +} + +uint64_t load_4(const unsigned char *in) +{ + uint64_t result; + result = (uint64_t) in[0]; + result |= ((uint64_t) in[1]) << 8; + result |= ((uint64_t) in[2]) << 16; + result |= ((uint64_t) in[3]) << 24; + return result; +} + +/* From fe_0.c */ + +/* +h = 0 +*/ + +void fe_0(fe h) { + h[0] = 0; + h[1] = 0; + h[2] = 0; + h[3] = 0; + h[4] = 0; + h[5] = 0; + h[6] = 0; + h[7] = 0; + h[8] = 0; + h[9] = 0; +} + +/* From fe_1.c */ + +/* +h = 1 +*/ + +static void fe_1(fe h) { + h[0] = 1; + h[1] = 0; + h[2] = 0; + h[3] = 0; + h[4] = 0; + h[5] = 0; + h[6] = 0; + h[7] = 0; + h[8] = 0; + h[9] = 0; +} + +/* From fe_add.c */ + +/* +h = f + g +Can overlap h with f or g. + +Preconditions: + |f| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc. + |g| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc. + +Postconditions: + |h| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc. +*/ + +void fe_add(fe h, const fe f, const fe g) { + int32_t f0 = f[0]; + int32_t f1 = f[1]; + int32_t f2 = f[2]; + int32_t f3 = f[3]; + int32_t f4 = f[4]; + int32_t f5 = f[5]; + int32_t f6 = f[6]; + int32_t f7 = f[7]; + int32_t f8 = f[8]; + int32_t f9 = f[9]; + int32_t g0 = g[0]; + int32_t g1 = g[1]; + int32_t g2 = g[2]; + int32_t g3 = g[3]; + int32_t g4 = g[4]; + int32_t g5 = g[5]; + int32_t g6 = g[6]; + int32_t g7 = g[7]; + int32_t g8 = g[8]; + int32_t g9 = g[9]; + int32_t h0 = f0 + g0; + int32_t h1 = f1 + g1; + int32_t h2 = f2 + g2; + int32_t h3 = f3 + g3; + int32_t h4 = f4 + g4; + int32_t h5 = f5 + g5; + int32_t h6 = f6 + g6; + int32_t h7 = f7 + g7; + int32_t h8 = f8 + g8; + int32_t h9 = f9 + g9; + h[0] = h0; + h[1] = h1; + h[2] = h2; + h[3] = h3; + h[4] = h4; + h[5] = h5; + h[6] = h6; + h[7] = h7; + h[8] = h8; + h[9] = h9; +} + +/* From fe_cmov.c */ + +/* +Replace (f,g) with (g,g) if b == 1; +replace (f,g) with (f,g) if b == 0. + +Preconditions: b in {0,1}. +*/ + +static void fe_cmov(fe f, const fe g, unsigned int b) { + int32_t f0 = f[0]; + int32_t f1 = f[1]; + int32_t f2 = f[2]; + int32_t f3 = f[3]; + int32_t f4 = f[4]; + int32_t f5 = f[5]; + int32_t f6 = f[6]; + int32_t f7 = f[7]; + int32_t f8 = f[8]; + int32_t f9 = f[9]; + int32_t g0 = g[0]; + int32_t g1 = g[1]; + int32_t g2 = g[2]; + int32_t g3 = g[3]; + int32_t g4 = g[4]; + int32_t g5 = g[5]; + int32_t g6 = g[6]; + int32_t g7 = g[7]; + int32_t g8 = g[8]; + int32_t g9 = g[9]; + int32_t x0 = f0 ^ g0; + int32_t x1 = f1 ^ g1; + int32_t x2 = f2 ^ g2; + int32_t x3 = f3 ^ g3; + int32_t x4 = f4 ^ g4; + int32_t x5 = f5 ^ g5; + int32_t x6 = f6 ^ g6; + int32_t x7 = f7 ^ g7; + int32_t x8 = f8 ^ g8; + int32_t x9 = f9 ^ g9; + assert((((b - 1) & ~b) | ((b - 2) & ~(b - 1))) == (unsigned int) -1); + b = -b; + x0 &= b; + x1 &= b; + x2 &= b; + x3 &= b; + x4 &= b; + x5 &= b; + x6 &= b; + x7 &= b; + x8 &= b; + x9 &= b; + f[0] = f0 ^ x0; + f[1] = f1 ^ x1; + f[2] = f2 ^ x2; + f[3] = f3 ^ x3; + f[4] = f4 ^ x4; + f[5] = f5 ^ x5; + f[6] = f6 ^ x6; + f[7] = f7 ^ x7; + f[8] = f8 ^ x8; + f[9] = f9 ^ x9; +} + +/* From fe_copy.c */ + +/* +h = f +*/ + +static void fe_copy(fe h, const fe f) { + int32_t f0 = f[0]; + int32_t f1 = f[1]; + int32_t f2 = f[2]; + int32_t f3 = f[3]; + int32_t f4 = f[4]; + int32_t f5 = f[5]; + int32_t f6 = f[6]; + int32_t f7 = f[7]; + int32_t f8 = f[8]; + int32_t f9 = f[9]; + h[0] = f0; + h[1] = f1; + h[2] = f2; + h[3] = f3; + h[4] = f4; + h[5] = f5; + h[6] = f6; + h[7] = f7; + h[8] = f8; + h[9] = f9; +} + +/* From fe_invert.c */ + +void fe_invert(fe out, const fe z) { + fe t0; + fe t1; + fe t2; + fe t3; + int i; + + fe_sq(t0, z); + fe_sq(t1, t0); + fe_sq(t1, t1); + fe_mul(t1, z, t1); + fe_mul(t0, t0, t1); + fe_sq(t2, t0); + fe_mul(t1, t1, t2); + fe_sq(t2, t1); + for (i = 0; i < 4; ++i) { + fe_sq(t2, t2); + } + fe_mul(t1, t2, t1); + fe_sq(t2, t1); + for (i = 0; i < 9; ++i) { + fe_sq(t2, t2); + } + fe_mul(t2, t2, t1); + fe_sq(t3, t2); + for (i = 0; i < 19; ++i) { + fe_sq(t3, t3); + } + fe_mul(t2, t3, t2); + fe_sq(t2, t2); + for (i = 0; i < 9; ++i) { + fe_sq(t2, t2); + } + fe_mul(t1, t2, t1); + fe_sq(t2, t1); + for (i = 0; i < 49; ++i) { + fe_sq(t2, t2); + } + fe_mul(t2, t2, t1); + fe_sq(t3, t2); + for (i = 0; i < 99; ++i) { + fe_sq(t3, t3); + } + fe_mul(t2, t3, t2); + fe_sq(t2, t2); + for (i = 0; i < 49; ++i) { + fe_sq(t2, t2); + } + fe_mul(t1, t2, t1); + fe_sq(t1, t1); + for (i = 0; i < 4; ++i) { + fe_sq(t1, t1); + } + fe_mul(out, t1, t0); + + return; +} + +/* From fe_isnegative.c */ + +/* +return 1 if f is in {1,3,5,...,q-2} +return 0 if f is in {0,2,4,...,q-1} + +Preconditions: + |f| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc. +*/ + +static int fe_isnegative(const fe f) { + unsigned char s[32]; + fe_tobytes(s, f); + return s[0] & 1; +} + +/* From fe_isnonzero.c, modified */ + +static int fe_isnonzero(const fe f) { + unsigned char s[32]; + fe_tobytes(s, f); + return (((int) (s[0] | s[1] | s[2] | s[3] | s[4] | s[5] | s[6] | s[7] | s[8] | + s[9] | s[10] | s[11] | s[12] | s[13] | s[14] | s[15] | s[16] | s[17] | + s[18] | s[19] | s[20] | s[21] | s[22] | s[23] | s[24] | s[25] | s[26] | + s[27] | s[28] | s[29] | s[30] | s[31]) - 1) >> 8) + 1; +} + +/* From fe_mul.c */ + +/* +h = f * g +Can overlap h with f or g. + +Preconditions: + |f| bounded by 1.65*2^26,1.65*2^25,1.65*2^26,1.65*2^25,etc. + |g| bounded by 1.65*2^26,1.65*2^25,1.65*2^26,1.65*2^25,etc. + +Postconditions: + |h| bounded by 1.01*2^25,1.01*2^24,1.01*2^25,1.01*2^24,etc. +*/ + +/* +Notes on implementation strategy: + +Using schoolbook multiplication. +Karatsuba would save a little in some cost models. + +Most multiplications by 2 and 19 are 32-bit precomputations; +cheaper than 64-bit postcomputations. + +There is one remaining multiplication by 19 in the carry chain; +one *19 precomputation can be merged into this, +but the resulting data flow is considerably less clean. + +There are 12 carries below. +10 of them are 2-way parallelizable and vectorizable. +Can get away with 11 carries, but then data flow is much deeper. + +With tighter constraints on inputs can squeeze carries into int32. +*/ + +void fe_mul(fe h, const fe f, const fe g) { + int32_t f0 = f[0]; + int32_t f1 = f[1]; + int32_t f2 = f[2]; + int32_t f3 = f[3]; + int32_t f4 = f[4]; + int32_t f5 = f[5]; + int32_t f6 = f[6]; + int32_t f7 = f[7]; + int32_t f8 = f[8]; + int32_t f9 = f[9]; + int32_t g0 = g[0]; + int32_t g1 = g[1]; + int32_t g2 = g[2]; + int32_t g3 = g[3]; + int32_t g4 = g[4]; + int32_t g5 = g[5]; + int32_t g6 = g[6]; + int32_t g7 = g[7]; + int32_t g8 = g[8]; + int32_t g9 = g[9]; + int32_t g1_19 = 19 * g1; /* 1.959375*2^29 */ + int32_t g2_19 = 19 * g2; /* 1.959375*2^30; still ok */ + int32_t g3_19 = 19 * g3; + int32_t g4_19 = 19 * g4; + int32_t g5_19 = 19 * g5; + int32_t g6_19 = 19 * g6; + int32_t g7_19 = 19 * g7; + int32_t g8_19 = 19 * g8; + int32_t g9_19 = 19 * g9; + int32_t f1_2 = 2 * f1; + int32_t f3_2 = 2 * f3; + int32_t f5_2 = 2 * f5; + int32_t f7_2 = 2 * f7; + int32_t f9_2 = 2 * f9; + int64_t f0g0 = f0 * (int64_t) g0; + int64_t f0g1 = f0 * (int64_t) g1; + int64_t f0g2 = f0 * (int64_t) g2; + int64_t f0g3 = f0 * (int64_t) g3; + int64_t f0g4 = f0 * (int64_t) g4; + int64_t f0g5 = f0 * (int64_t) g5; + int64_t f0g6 = f0 * (int64_t) g6; + int64_t f0g7 = f0 * (int64_t) g7; + int64_t f0g8 = f0 * (int64_t) g8; + int64_t f0g9 = f0 * (int64_t) g9; + int64_t f1g0 = f1 * (int64_t) g0; + int64_t f1g1_2 = f1_2 * (int64_t) g1; + int64_t f1g2 = f1 * (int64_t) g2; + int64_t f1g3_2 = f1_2 * (int64_t) g3; + int64_t f1g4 = f1 * (int64_t) g4; + int64_t f1g5_2 = f1_2 * (int64_t) g5; + int64_t f1g6 = f1 * (int64_t) g6; + int64_t f1g7_2 = f1_2 * (int64_t) g7; + int64_t f1g8 = f1 * (int64_t) g8; + int64_t f1g9_38 = f1_2 * (int64_t) g9_19; + int64_t f2g0 = f2 * (int64_t) g0; + int64_t f2g1 = f2 * (int64_t) g1; + int64_t f2g2 = f2 * (int64_t) g2; + int64_t f2g3 = f2 * (int64_t) g3; + int64_t f2g4 = f2 * (int64_t) g4; + int64_t f2g5 = f2 * (int64_t) g5; + int64_t f2g6 = f2 * (int64_t) g6; + int64_t f2g7 = f2 * (int64_t) g7; + int64_t f2g8_19 = f2 * (int64_t) g8_19; + int64_t f2g9_19 = f2 * (int64_t) g9_19; + int64_t f3g0 = f3 * (int64_t) g0; + int64_t f3g1_2 = f3_2 * (int64_t) g1; + int64_t f3g2 = f3 * (int64_t) g2; + int64_t f3g3_2 = f3_2 * (int64_t) g3; + int64_t f3g4 = f3 * (int64_t) g4; + int64_t f3g5_2 = f3_2 * (int64_t) g5; + int64_t f3g6 = f3 * (int64_t) g6; + int64_t f3g7_38 = f3_2 * (int64_t) g7_19; + int64_t f3g8_19 = f3 * (int64_t) g8_19; + int64_t f3g9_38 = f3_2 * (int64_t) g9_19; + int64_t f4g0 = f4 * (int64_t) g0; + int64_t f4g1 = f4 * (int64_t) g1; + int64_t f4g2 = f4 * (int64_t) g2; + int64_t f4g3 = f4 * (int64_t) g3; + int64_t f4g4 = f4 * (int64_t) g4; + int64_t f4g5 = f4 * (int64_t) g5; + int64_t f4g6_19 = f4 * (int64_t) g6_19; + int64_t f4g7_19 = f4 * (int64_t) g7_19; + int64_t f4g8_19 = f4 * (int64_t) g8_19; + int64_t f4g9_19 = f4 * (int64_t) g9_19; + int64_t f5g0 = f5 * (int64_t) g0; + int64_t f5g1_2 = f5_2 * (int64_t) g1; + int64_t f5g2 = f5 * (int64_t) g2; + int64_t f5g3_2 = f5_2 * (int64_t) g3; + int64_t f5g4 = f5 * (int64_t) g4; + int64_t f5g5_38 = f5_2 * (int64_t) g5_19; + int64_t f5g6_19 = f5 * (int64_t) g6_19; + int64_t f5g7_38 = f5_2 * (int64_t) g7_19; + int64_t f5g8_19 = f5 * (int64_t) g8_19; + int64_t f5g9_38 = f5_2 * (int64_t) g9_19; + int64_t f6g0 = f6 * (int64_t) g0; + int64_t f6g1 = f6 * (int64_t) g1; + int64_t f6g2 = f6 * (int64_t) g2; + int64_t f6g3 = f6 * (int64_t) g3; + int64_t f6g4_19 = f6 * (int64_t) g4_19; + int64_t f6g5_19 = f6 * (int64_t) g5_19; + int64_t f6g6_19 = f6 * (int64_t) g6_19; + int64_t f6g7_19 = f6 * (int64_t) g7_19; + int64_t f6g8_19 = f6 * (int64_t) g8_19; + int64_t f6g9_19 = f6 * (int64_t) g9_19; + int64_t f7g0 = f7 * (int64_t) g0; + int64_t f7g1_2 = f7_2 * (int64_t) g1; + int64_t f7g2 = f7 * (int64_t) g2; + int64_t f7g3_38 = f7_2 * (int64_t) g3_19; + int64_t f7g4_19 = f7 * (int64_t) g4_19; + int64_t f7g5_38 = f7_2 * (int64_t) g5_19; + int64_t f7g6_19 = f7 * (int64_t) g6_19; + int64_t f7g7_38 = f7_2 * (int64_t) g7_19; + int64_t f7g8_19 = f7 * (int64_t) g8_19; + int64_t f7g9_38 = f7_2 * (int64_t) g9_19; + int64_t f8g0 = f8 * (int64_t) g0; + int64_t f8g1 = f8 * (int64_t) g1; + int64_t f8g2_19 = f8 * (int64_t) g2_19; + int64_t f8g3_19 = f8 * (int64_t) g3_19; + int64_t f8g4_19 = f8 * (int64_t) g4_19; + int64_t f8g5_19 = f8 * (int64_t) g5_19; + int64_t f8g6_19 = f8 * (int64_t) g6_19; + int64_t f8g7_19 = f8 * (int64_t) g7_19; + int64_t f8g8_19 = f8 * (int64_t) g8_19; + int64_t f8g9_19 = f8 * (int64_t) g9_19; + int64_t f9g0 = f9 * (int64_t) g0; + int64_t f9g1_38 = f9_2 * (int64_t) g1_19; + int64_t f9g2_19 = f9 * (int64_t) g2_19; + int64_t f9g3_38 = f9_2 * (int64_t) g3_19; + int64_t f9g4_19 = f9 * (int64_t) g4_19; + int64_t f9g5_38 = f9_2 * (int64_t) g5_19; + int64_t f9g6_19 = f9 * (int64_t) g6_19; + int64_t f9g7_38 = f9_2 * (int64_t) g7_19; + int64_t f9g8_19 = f9 * (int64_t) g8_19; + int64_t f9g9_38 = f9_2 * (int64_t) g9_19; + int64_t h0 = f0g0+f1g9_38+f2g8_19+f3g7_38+f4g6_19+f5g5_38+f6g4_19+f7g3_38+f8g2_19+f9g1_38; + int64_t h1 = f0g1+f1g0 +f2g9_19+f3g8_19+f4g7_19+f5g6_19+f6g5_19+f7g4_19+f8g3_19+f9g2_19; + int64_t h2 = f0g2+f1g1_2 +f2g0 +f3g9_38+f4g8_19+f5g7_38+f6g6_19+f7g5_38+f8g4_19+f9g3_38; + int64_t h3 = f0g3+f1g2 +f2g1 +f3g0 +f4g9_19+f5g8_19+f6g7_19+f7g6_19+f8g5_19+f9g4_19; + int64_t h4 = f0g4+f1g3_2 +f2g2 +f3g1_2 +f4g0 +f5g9_38+f6g8_19+f7g7_38+f8g6_19+f9g5_38; + int64_t h5 = f0g5+f1g4 +f2g3 +f3g2 +f4g1 +f5g0 +f6g9_19+f7g8_19+f8g7_19+f9g6_19; + int64_t h6 = f0g6+f1g5_2 +f2g4 +f3g3_2 +f4g2 +f5g1_2 +f6g0 +f7g9_38+f8g8_19+f9g7_38; + int64_t h7 = f0g7+f1g6 +f2g5 +f3g4 +f4g3 +f5g2 +f6g1 +f7g0 +f8g9_19+f9g8_19; + int64_t h8 = f0g8+f1g7_2 +f2g6 +f3g5_2 +f4g4 +f5g3_2 +f6g2 +f7g1_2 +f8g0 +f9g9_38; + int64_t h9 = f0g9+f1g8 +f2g7 +f3g6 +f4g5 +f5g4 +f6g3 +f7g2 +f8g1 +f9g0 ; + int64_t carry0; + int64_t carry1; + int64_t carry2; + int64_t carry3; + int64_t carry4; + int64_t carry5; + int64_t carry6; + int64_t carry7; + int64_t carry8; + int64_t carry9; + + /* + |h0| <= (1.65*1.65*2^52*(1+19+19+19+19)+1.65*1.65*2^50*(38+38+38+38+38)) + i.e. |h0| <= 1.4*2^60; narrower ranges for h2, h4, h6, h8 + |h1| <= (1.65*1.65*2^51*(1+1+19+19+19+19+19+19+19+19)) + i.e. |h1| <= 1.7*2^59; narrower ranges for h3, h5, h7, h9 + */ + + carry0 = (h0 + (int64_t) (1<<25)) >> 26; h1 += carry0; h0 -= carry0 << 26; + carry4 = (h4 + (int64_t) (1<<25)) >> 26; h5 += carry4; h4 -= carry4 << 26; + /* |h0| <= 2^25 */ + /* |h4| <= 2^25 */ + /* |h1| <= 1.71*2^59 */ + /* |h5| <= 1.71*2^59 */ + + carry1 = (h1 + (int64_t) (1<<24)) >> 25; h2 += carry1; h1 -= carry1 << 25; + carry5 = (h5 + (int64_t) (1<<24)) >> 25; h6 += carry5; h5 -= carry5 << 25; + /* |h1| <= 2^24; from now on fits into int32 */ + /* |h5| <= 2^24; from now on fits into int32 */ + /* |h2| <= 1.41*2^60 */ + /* |h6| <= 1.41*2^60 */ + + carry2 = (h2 + (int64_t) (1<<25)) >> 26; h3 += carry2; h2 -= carry2 << 26; + carry6 = (h6 + (int64_t) (1<<25)) >> 26; h7 += carry6; h6 -= carry6 << 26; + /* |h2| <= 2^25; from now on fits into int32 unchanged */ + /* |h6| <= 2^25; from now on fits into int32 unchanged */ + /* |h3| <= 1.71*2^59 */ + /* |h7| <= 1.71*2^59 */ + + carry3 = (h3 + (int64_t) (1<<24)) >> 25; h4 += carry3; h3 -= carry3 << 25; + carry7 = (h7 + (int64_t) (1<<24)) >> 25; h8 += carry7; h7 -= carry7 << 25; + /* |h3| <= 2^24; from now on fits into int32 unchanged */ + /* |h7| <= 2^24; from now on fits into int32 unchanged */ + /* |h4| <= 1.72*2^34 */ + /* |h8| <= 1.41*2^60 */ + + carry4 = (h4 + (int64_t) (1<<25)) >> 26; h5 += carry4; h4 -= carry4 << 26; + carry8 = (h8 + (int64_t) (1<<25)) >> 26; h9 += carry8; h8 -= carry8 << 26; + /* |h4| <= 2^25; from now on fits into int32 unchanged */ + /* |h8| <= 2^25; from now on fits into int32 unchanged */ + /* |h5| <= 1.01*2^24 */ + /* |h9| <= 1.71*2^59 */ + + carry9 = (h9 + (int64_t) (1<<24)) >> 25; h0 += carry9 * 19; h9 -= carry9 << 25; + /* |h9| <= 2^24; from now on fits into int32 unchanged */ + /* |h0| <= 1.1*2^39 */ + + carry0 = (h0 + (int64_t) (1<<25)) >> 26; h1 += carry0; h0 -= carry0 << 26; + /* |h0| <= 2^25; from now on fits into int32 unchanged */ + /* |h1| <= 1.01*2^24 */ + + h[0] = h0; + h[1] = h1; + h[2] = h2; + h[3] = h3; + h[4] = h4; + h[5] = h5; + h[6] = h6; + h[7] = h7; + h[8] = h8; + h[9] = h9; +} + +/* From fe_neg.c */ + +/* +h = -f + +Preconditions: + |f| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc. + +Postconditions: + |h| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc. +*/ + +static void fe_neg(fe h, const fe f) { + int32_t f0 = f[0]; + int32_t f1 = f[1]; + int32_t f2 = f[2]; + int32_t f3 = f[3]; + int32_t f4 = f[4]; + int32_t f5 = f[5]; + int32_t f6 = f[6]; + int32_t f7 = f[7]; + int32_t f8 = f[8]; + int32_t f9 = f[9]; + int32_t h0 = -f0; + int32_t h1 = -f1; + int32_t h2 = -f2; + int32_t h3 = -f3; + int32_t h4 = -f4; + int32_t h5 = -f5; + int32_t h6 = -f6; + int32_t h7 = -f7; + int32_t h8 = -f8; + int32_t h9 = -f9; + h[0] = h0; + h[1] = h1; + h[2] = h2; + h[3] = h3; + h[4] = h4; + h[5] = h5; + h[6] = h6; + h[7] = h7; + h[8] = h8; + h[9] = h9; +} + +/* From fe_sq.c */ + +/* +h = f * f +Can overlap h with f. + +Preconditions: + |f| bounded by 1.65*2^26,1.65*2^25,1.65*2^26,1.65*2^25,etc. + +Postconditions: + |h| bounded by 1.01*2^25,1.01*2^24,1.01*2^25,1.01*2^24,etc. +*/ + +/* +See fe_mul.c for discussion of implementation strategy. +*/ + +static void fe_sq(fe h, const fe f) { + int32_t f0 = f[0]; + int32_t f1 = f[1]; + int32_t f2 = f[2]; + int32_t f3 = f[3]; + int32_t f4 = f[4]; + int32_t f5 = f[5]; + int32_t f6 = f[6]; + int32_t f7 = f[7]; + int32_t f8 = f[8]; + int32_t f9 = f[9]; + int32_t f0_2 = 2 * f0; + int32_t f1_2 = 2 * f1; + int32_t f2_2 = 2 * f2; + int32_t f3_2 = 2 * f3; + int32_t f4_2 = 2 * f4; + int32_t f5_2 = 2 * f5; + int32_t f6_2 = 2 * f6; + int32_t f7_2 = 2 * f7; + int32_t f5_38 = 38 * f5; /* 1.959375*2^30 */ + int32_t f6_19 = 19 * f6; /* 1.959375*2^30 */ + int32_t f7_38 = 38 * f7; /* 1.959375*2^30 */ + int32_t f8_19 = 19 * f8; /* 1.959375*2^30 */ + int32_t f9_38 = 38 * f9; /* 1.959375*2^30 */ + int64_t f0f0 = f0 * (int64_t) f0; + int64_t f0f1_2 = f0_2 * (int64_t) f1; + int64_t f0f2_2 = f0_2 * (int64_t) f2; + int64_t f0f3_2 = f0_2 * (int64_t) f3; + int64_t f0f4_2 = f0_2 * (int64_t) f4; + int64_t f0f5_2 = f0_2 * (int64_t) f5; + int64_t f0f6_2 = f0_2 * (int64_t) f6; + int64_t f0f7_2 = f0_2 * (int64_t) f7; + int64_t f0f8_2 = f0_2 * (int64_t) f8; + int64_t f0f9_2 = f0_2 * (int64_t) f9; + int64_t f1f1_2 = f1_2 * (int64_t) f1; + int64_t f1f2_2 = f1_2 * (int64_t) f2; + int64_t f1f3_4 = f1_2 * (int64_t) f3_2; + int64_t f1f4_2 = f1_2 * (int64_t) f4; + int64_t f1f5_4 = f1_2 * (int64_t) f5_2; + int64_t f1f6_2 = f1_2 * (int64_t) f6; + int64_t f1f7_4 = f1_2 * (int64_t) f7_2; + int64_t f1f8_2 = f1_2 * (int64_t) f8; + int64_t f1f9_76 = f1_2 * (int64_t) f9_38; + int64_t f2f2 = f2 * (int64_t) f2; + int64_t f2f3_2 = f2_2 * (int64_t) f3; + int64_t f2f4_2 = f2_2 * (int64_t) f4; + int64_t f2f5_2 = f2_2 * (int64_t) f5; + int64_t f2f6_2 = f2_2 * (int64_t) f6; + int64_t f2f7_2 = f2_2 * (int64_t) f7; + int64_t f2f8_38 = f2_2 * (int64_t) f8_19; + int64_t f2f9_38 = f2 * (int64_t) f9_38; + int64_t f3f3_2 = f3_2 * (int64_t) f3; + int64_t f3f4_2 = f3_2 * (int64_t) f4; + int64_t f3f5_4 = f3_2 * (int64_t) f5_2; + int64_t f3f6_2 = f3_2 * (int64_t) f6; + int64_t f3f7_76 = f3_2 * (int64_t) f7_38; + int64_t f3f8_38 = f3_2 * (int64_t) f8_19; + int64_t f3f9_76 = f3_2 * (int64_t) f9_38; + int64_t f4f4 = f4 * (int64_t) f4; + int64_t f4f5_2 = f4_2 * (int64_t) f5; + int64_t f4f6_38 = f4_2 * (int64_t) f6_19; + int64_t f4f7_38 = f4 * (int64_t) f7_38; + int64_t f4f8_38 = f4_2 * (int64_t) f8_19; + int64_t f4f9_38 = f4 * (int64_t) f9_38; + int64_t f5f5_38 = f5 * (int64_t) f5_38; + int64_t f5f6_38 = f5_2 * (int64_t) f6_19; + int64_t f5f7_76 = f5_2 * (int64_t) f7_38; + int64_t f5f8_38 = f5_2 * (int64_t) f8_19; + int64_t f5f9_76 = f5_2 * (int64_t) f9_38; + int64_t f6f6_19 = f6 * (int64_t) f6_19; + int64_t f6f7_38 = f6 * (int64_t) f7_38; + int64_t f6f8_38 = f6_2 * (int64_t) f8_19; + int64_t f6f9_38 = f6 * (int64_t) f9_38; + int64_t f7f7_38 = f7 * (int64_t) f7_38; + int64_t f7f8_38 = f7_2 * (int64_t) f8_19; + int64_t f7f9_76 = f7_2 * (int64_t) f9_38; + int64_t f8f8_19 = f8 * (int64_t) f8_19; + int64_t f8f9_38 = f8 * (int64_t) f9_38; + int64_t f9f9_38 = f9 * (int64_t) f9_38; + int64_t h0 = f0f0 +f1f9_76+f2f8_38+f3f7_76+f4f6_38+f5f5_38; + int64_t h1 = f0f1_2+f2f9_38+f3f8_38+f4f7_38+f5f6_38; + int64_t h2 = f0f2_2+f1f1_2 +f3f9_76+f4f8_38+f5f7_76+f6f6_19; + int64_t h3 = f0f3_2+f1f2_2 +f4f9_38+f5f8_38+f6f7_38; + int64_t h4 = f0f4_2+f1f3_4 +f2f2 +f5f9_76+f6f8_38+f7f7_38; + int64_t h5 = f0f5_2+f1f4_2 +f2f3_2 +f6f9_38+f7f8_38; + int64_t h6 = f0f6_2+f1f5_4 +f2f4_2 +f3f3_2 +f7f9_76+f8f8_19; + int64_t h7 = f0f7_2+f1f6_2 +f2f5_2 +f3f4_2 +f8f9_38; + int64_t h8 = f0f8_2+f1f7_4 +f2f6_2 +f3f5_4 +f4f4 +f9f9_38; + int64_t h9 = f0f9_2+f1f8_2 +f2f7_2 +f3f6_2 +f4f5_2; + int64_t carry0; + int64_t carry1; + int64_t carry2; + int64_t carry3; + int64_t carry4; + int64_t carry5; + int64_t carry6; + int64_t carry7; + int64_t carry8; + int64_t carry9; + + carry0 = (h0 + (int64_t) (1<<25)) >> 26; h1 += carry0; h0 -= carry0 << 26; + carry4 = (h4 + (int64_t) (1<<25)) >> 26; h5 += carry4; h4 -= carry4 << 26; + + carry1 = (h1 + (int64_t) (1<<24)) >> 25; h2 += carry1; h1 -= carry1 << 25; + carry5 = (h5 + (int64_t) (1<<24)) >> 25; h6 += carry5; h5 -= carry5 << 25; + + carry2 = (h2 + (int64_t) (1<<25)) >> 26; h3 += carry2; h2 -= carry2 << 26; + carry6 = (h6 + (int64_t) (1<<25)) >> 26; h7 += carry6; h6 -= carry6 << 26; + + carry3 = (h3 + (int64_t) (1<<24)) >> 25; h4 += carry3; h3 -= carry3 << 25; + carry7 = (h7 + (int64_t) (1<<24)) >> 25; h8 += carry7; h7 -= carry7 << 25; + + carry4 = (h4 + (int64_t) (1<<25)) >> 26; h5 += carry4; h4 -= carry4 << 26; + carry8 = (h8 + (int64_t) (1<<25)) >> 26; h9 += carry8; h8 -= carry8 << 26; + + carry9 = (h9 + (int64_t) (1<<24)) >> 25; h0 += carry9 * 19; h9 -= carry9 << 25; + + carry0 = (h0 + (int64_t) (1<<25)) >> 26; h1 += carry0; h0 -= carry0 << 26; + + h[0] = h0; + h[1] = h1; + h[2] = h2; + h[3] = h3; + h[4] = h4; + h[5] = h5; + h[6] = h6; + h[7] = h7; + h[8] = h8; + h[9] = h9; +} + +/* From fe_sq2.c */ + +/* +h = 2 * f * f +Can overlap h with f. + +Preconditions: + |f| bounded by 1.65*2^26,1.65*2^25,1.65*2^26,1.65*2^25,etc. + +Postconditions: + |h| bounded by 1.01*2^25,1.01*2^24,1.01*2^25,1.01*2^24,etc. +*/ + +/* +See fe_mul.c for discussion of implementation strategy. +*/ + +static void fe_sq2(fe h, const fe f) { + int32_t f0 = f[0]; + int32_t f1 = f[1]; + int32_t f2 = f[2]; + int32_t f3 = f[3]; + int32_t f4 = f[4]; + int32_t f5 = f[5]; + int32_t f6 = f[6]; + int32_t f7 = f[7]; + int32_t f8 = f[8]; + int32_t f9 = f[9]; + int32_t f0_2 = 2 * f0; + int32_t f1_2 = 2 * f1; + int32_t f2_2 = 2 * f2; + int32_t f3_2 = 2 * f3; + int32_t f4_2 = 2 * f4; + int32_t f5_2 = 2 * f5; + int32_t f6_2 = 2 * f6; + int32_t f7_2 = 2 * f7; + int32_t f5_38 = 38 * f5; /* 1.959375*2^30 */ + int32_t f6_19 = 19 * f6; /* 1.959375*2^30 */ + int32_t f7_38 = 38 * f7; /* 1.959375*2^30 */ + int32_t f8_19 = 19 * f8; /* 1.959375*2^30 */ + int32_t f9_38 = 38 * f9; /* 1.959375*2^30 */ + int64_t f0f0 = f0 * (int64_t) f0; + int64_t f0f1_2 = f0_2 * (int64_t) f1; + int64_t f0f2_2 = f0_2 * (int64_t) f2; + int64_t f0f3_2 = f0_2 * (int64_t) f3; + int64_t f0f4_2 = f0_2 * (int64_t) f4; + int64_t f0f5_2 = f0_2 * (int64_t) f5; + int64_t f0f6_2 = f0_2 * (int64_t) f6; + int64_t f0f7_2 = f0_2 * (int64_t) f7; + int64_t f0f8_2 = f0_2 * (int64_t) f8; + int64_t f0f9_2 = f0_2 * (int64_t) f9; + int64_t f1f1_2 = f1_2 * (int64_t) f1; + int64_t f1f2_2 = f1_2 * (int64_t) f2; + int64_t f1f3_4 = f1_2 * (int64_t) f3_2; + int64_t f1f4_2 = f1_2 * (int64_t) f4; + int64_t f1f5_4 = f1_2 * (int64_t) f5_2; + int64_t f1f6_2 = f1_2 * (int64_t) f6; + int64_t f1f7_4 = f1_2 * (int64_t) f7_2; + int64_t f1f8_2 = f1_2 * (int64_t) f8; + int64_t f1f9_76 = f1_2 * (int64_t) f9_38; + int64_t f2f2 = f2 * (int64_t) f2; + int64_t f2f3_2 = f2_2 * (int64_t) f3; + int64_t f2f4_2 = f2_2 * (int64_t) f4; + int64_t f2f5_2 = f2_2 * (int64_t) f5; + int64_t f2f6_2 = f2_2 * (int64_t) f6; + int64_t f2f7_2 = f2_2 * (int64_t) f7; + int64_t f2f8_38 = f2_2 * (int64_t) f8_19; + int64_t f2f9_38 = f2 * (int64_t) f9_38; + int64_t f3f3_2 = f3_2 * (int64_t) f3; + int64_t f3f4_2 = f3_2 * (int64_t) f4; + int64_t f3f5_4 = f3_2 * (int64_t) f5_2; + int64_t f3f6_2 = f3_2 * (int64_t) f6; + int64_t f3f7_76 = f3_2 * (int64_t) f7_38; + int64_t f3f8_38 = f3_2 * (int64_t) f8_19; + int64_t f3f9_76 = f3_2 * (int64_t) f9_38; + int64_t f4f4 = f4 * (int64_t) f4; + int64_t f4f5_2 = f4_2 * (int64_t) f5; + int64_t f4f6_38 = f4_2 * (int64_t) f6_19; + int64_t f4f7_38 = f4 * (int64_t) f7_38; + int64_t f4f8_38 = f4_2 * (int64_t) f8_19; + int64_t f4f9_38 = f4 * (int64_t) f9_38; + int64_t f5f5_38 = f5 * (int64_t) f5_38; + int64_t f5f6_38 = f5_2 * (int64_t) f6_19; + int64_t f5f7_76 = f5_2 * (int64_t) f7_38; + int64_t f5f8_38 = f5_2 * (int64_t) f8_19; + int64_t f5f9_76 = f5_2 * (int64_t) f9_38; + int64_t f6f6_19 = f6 * (int64_t) f6_19; + int64_t f6f7_38 = f6 * (int64_t) f7_38; + int64_t f6f8_38 = f6_2 * (int64_t) f8_19; + int64_t f6f9_38 = f6 * (int64_t) f9_38; + int64_t f7f7_38 = f7 * (int64_t) f7_38; + int64_t f7f8_38 = f7_2 * (int64_t) f8_19; + int64_t f7f9_76 = f7_2 * (int64_t) f9_38; + int64_t f8f8_19 = f8 * (int64_t) f8_19; + int64_t f8f9_38 = f8 * (int64_t) f9_38; + int64_t f9f9_38 = f9 * (int64_t) f9_38; + int64_t h0 = f0f0 +f1f9_76+f2f8_38+f3f7_76+f4f6_38+f5f5_38; + int64_t h1 = f0f1_2+f2f9_38+f3f8_38+f4f7_38+f5f6_38; + int64_t h2 = f0f2_2+f1f1_2 +f3f9_76+f4f8_38+f5f7_76+f6f6_19; + int64_t h3 = f0f3_2+f1f2_2 +f4f9_38+f5f8_38+f6f7_38; + int64_t h4 = f0f4_2+f1f3_4 +f2f2 +f5f9_76+f6f8_38+f7f7_38; + int64_t h5 = f0f5_2+f1f4_2 +f2f3_2 +f6f9_38+f7f8_38; + int64_t h6 = f0f6_2+f1f5_4 +f2f4_2 +f3f3_2 +f7f9_76+f8f8_19; + int64_t h7 = f0f7_2+f1f6_2 +f2f5_2 +f3f4_2 +f8f9_38; + int64_t h8 = f0f8_2+f1f7_4 +f2f6_2 +f3f5_4 +f4f4 +f9f9_38; + int64_t h9 = f0f9_2+f1f8_2 +f2f7_2 +f3f6_2 +f4f5_2; + int64_t carry0; + int64_t carry1; + int64_t carry2; + int64_t carry3; + int64_t carry4; + int64_t carry5; + int64_t carry6; + int64_t carry7; + int64_t carry8; + int64_t carry9; + + h0 += h0; + h1 += h1; + h2 += h2; + h3 += h3; + h4 += h4; + h5 += h5; + h6 += h6; + h7 += h7; + h8 += h8; + h9 += h9; + + carry0 = (h0 + (int64_t) (1<<25)) >> 26; h1 += carry0; h0 -= carry0 << 26; + carry4 = (h4 + (int64_t) (1<<25)) >> 26; h5 += carry4; h4 -= carry4 << 26; + + carry1 = (h1 + (int64_t) (1<<24)) >> 25; h2 += carry1; h1 -= carry1 << 25; + carry5 = (h5 + (int64_t) (1<<24)) >> 25; h6 += carry5; h5 -= carry5 << 25; + + carry2 = (h2 + (int64_t) (1<<25)) >> 26; h3 += carry2; h2 -= carry2 << 26; + carry6 = (h6 + (int64_t) (1<<25)) >> 26; h7 += carry6; h6 -= carry6 << 26; + + carry3 = (h3 + (int64_t) (1<<24)) >> 25; h4 += carry3; h3 -= carry3 << 25; + carry7 = (h7 + (int64_t) (1<<24)) >> 25; h8 += carry7; h7 -= carry7 << 25; + + carry4 = (h4 + (int64_t) (1<<25)) >> 26; h5 += carry4; h4 -= carry4 << 26; + carry8 = (h8 + (int64_t) (1<<25)) >> 26; h9 += carry8; h8 -= carry8 << 26; + + carry9 = (h9 + (int64_t) (1<<24)) >> 25; h0 += carry9 * 19; h9 -= carry9 << 25; + + carry0 = (h0 + (int64_t) (1<<25)) >> 26; h1 += carry0; h0 -= carry0 << 26; + + h[0] = h0; + h[1] = h1; + h[2] = h2; + h[3] = h3; + h[4] = h4; + h[5] = h5; + h[6] = h6; + h[7] = h7; + h[8] = h8; + h[9] = h9; +} + +/* From fe_sub.c */ + +/* +h = f - g +Can overlap h with f or g. + +Preconditions: + |f| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc. + |g| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc. + +Postconditions: + |h| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc. +*/ + +static void fe_sub(fe h, const fe f, const fe g) { + int32_t f0 = f[0]; + int32_t f1 = f[1]; + int32_t f2 = f[2]; + int32_t f3 = f[3]; + int32_t f4 = f[4]; + int32_t f5 = f[5]; + int32_t f6 = f[6]; + int32_t f7 = f[7]; + int32_t f8 = f[8]; + int32_t f9 = f[9]; + int32_t g0 = g[0]; + int32_t g1 = g[1]; + int32_t g2 = g[2]; + int32_t g3 = g[3]; + int32_t g4 = g[4]; + int32_t g5 = g[5]; + int32_t g6 = g[6]; + int32_t g7 = g[7]; + int32_t g8 = g[8]; + int32_t g9 = g[9]; + int32_t h0 = f0 - g0; + int32_t h1 = f1 - g1; + int32_t h2 = f2 - g2; + int32_t h3 = f3 - g3; + int32_t h4 = f4 - g4; + int32_t h5 = f5 - g5; + int32_t h6 = f6 - g6; + int32_t h7 = f7 - g7; + int32_t h8 = f8 - g8; + int32_t h9 = f9 - g9; + h[0] = h0; + h[1] = h1; + h[2] = h2; + h[3] = h3; + h[4] = h4; + h[5] = h5; + h[6] = h6; + h[7] = h7; + h[8] = h8; + h[9] = h9; +} + +/* From fe_tobytes.c */ + +/* +Preconditions: + |h| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc. + +Write p=2^255-19; q=floor(h/p). +Basic claim: q = floor(2^(-255)(h + 19 2^(-25)h9 + 2^(-1))). + +Proof: + Have |h|<=p so |q|<=1 so |19^2 2^(-255) q|<1/4. + Also have |h-2^230 h9|<2^231 so |19 2^(-255)(h-2^230 h9)|<1/4. + + Write y=2^(-1)-19^2 2^(-255)q-19 2^(-255)(h-2^230 h9). + Then 0> 25; + q = (h0 + q) >> 26; + q = (h1 + q) >> 25; + q = (h2 + q) >> 26; + q = (h3 + q) >> 25; + q = (h4 + q) >> 26; + q = (h5 + q) >> 25; + q = (h6 + q) >> 26; + q = (h7 + q) >> 25; + q = (h8 + q) >> 26; + q = (h9 + q) >> 25; + + /* Goal: Output h-(2^255-19)q, which is between 0 and 2^255-20. */ + h0 += 19 * q; + /* Goal: Output h-2^255 q, which is between 0 and 2^255-20. */ + + carry0 = h0 >> 26; h1 += carry0; h0 -= carry0 << 26; + carry1 = h1 >> 25; h2 += carry1; h1 -= carry1 << 25; + carry2 = h2 >> 26; h3 += carry2; h2 -= carry2 << 26; + carry3 = h3 >> 25; h4 += carry3; h3 -= carry3 << 25; + carry4 = h4 >> 26; h5 += carry4; h4 -= carry4 << 26; + carry5 = h5 >> 25; h6 += carry5; h5 -= carry5 << 25; + carry6 = h6 >> 26; h7 += carry6; h6 -= carry6 << 26; + carry7 = h7 >> 25; h8 += carry7; h7 -= carry7 << 25; + carry8 = h8 >> 26; h9 += carry8; h8 -= carry8 << 26; + carry9 = h9 >> 25; h9 -= carry9 << 25; + /* h10 = carry9 */ + + /* + Goal: Output h0+...+2^255 h10-2^255 q, which is between 0 and 2^255-20. + Have h0+...+2^230 h9 between 0 and 2^255-1; + evidently 2^255 h10-2^255 q = 0. + Goal: Output h0+...+2^230 h9. + */ + + s[0] = h0 >> 0; + s[1] = h0 >> 8; + s[2] = h0 >> 16; + s[3] = (h0 >> 24) | (h1 << 2); + s[4] = h1 >> 6; + s[5] = h1 >> 14; + s[6] = (h1 >> 22) | (h2 << 3); + s[7] = h2 >> 5; + s[8] = h2 >> 13; + s[9] = (h2 >> 21) | (h3 << 5); + s[10] = h3 >> 3; + s[11] = h3 >> 11; + s[12] = (h3 >> 19) | (h4 << 6); + s[13] = h4 >> 2; + s[14] = h4 >> 10; + s[15] = h4 >> 18; + s[16] = h5 >> 0; + s[17] = h5 >> 8; + s[18] = h5 >> 16; + s[19] = (h5 >> 24) | (h6 << 1); + s[20] = h6 >> 7; + s[21] = h6 >> 15; + s[22] = (h6 >> 23) | (h7 << 3); + s[23] = h7 >> 5; + s[24] = h7 >> 13; + s[25] = (h7 >> 21) | (h8 << 4); + s[26] = h8 >> 4; + s[27] = h8 >> 12; + s[28] = (h8 >> 20) | (h9 << 6); + s[29] = h9 >> 2; + s[30] = h9 >> 10; + s[31] = h9 >> 18; +} + +/* From ge_add.c */ + +void ge_add(ge_p1p1 *r, const ge_p3 *p, const ge_cached *q) { + fe t0; + fe_add(r->X, p->Y, p->X); + fe_sub(r->Y, p->Y, p->X); + fe_mul(r->Z, r->X, q->YplusX); + fe_mul(r->Y, r->Y, q->YminusX); + fe_mul(r->T, q->T2d, p->T); + fe_mul(r->X, p->Z, q->Z); + fe_add(t0, r->X, r->X); + fe_sub(r->X, r->Z, r->Y); + fe_add(r->Y, r->Z, r->Y); + fe_add(r->Z, t0, r->T); + fe_sub(r->T, t0, r->T); +} + +/* From ge_double_scalarmult.c, modified */ + +static void slide(signed char *r, const unsigned char *a) { + int i; + int b; + int k; + + for (i = 0; i < 256; ++i) { + r[i] = 1 & (a[i >> 3] >> (i & 7)); + } + + for (i = 0; i < 256; ++i) { + if (r[i]) { + for (b = 1; b <= 6 && i + b < 256; ++b) { + if (r[i + b]) { + if (r[i] + (r[i + b] << b) <= 15) { + r[i] += r[i + b] << b; r[i + b] = 0; + } else if (r[i] - (r[i + b] << b) >= -15) { + r[i] -= r[i + b] << b; + for (k = i + b; k < 256; ++k) { + if (!r[k]) { + r[k] = 1; + break; + } + r[k] = 0; + } + } else + break; + } + } + } + } +} + +void ge_dsm_precomp(ge_dsmp r, const ge_p3 *s) { + ge_p1p1 t; + ge_p3 s2, u; + ge_p3_to_cached(&r[0], s); + ge_p3_dbl(&t, s); ge_p1p1_to_p3(&s2, &t); + ge_add(&t, &s2, &r[0]); ge_p1p1_to_p3(&u, &t); ge_p3_to_cached(&r[1], &u); + ge_add(&t, &s2, &r[1]); ge_p1p1_to_p3(&u, &t); ge_p3_to_cached(&r[2], &u); + ge_add(&t, &s2, &r[2]); ge_p1p1_to_p3(&u, &t); ge_p3_to_cached(&r[3], &u); + ge_add(&t, &s2, &r[3]); ge_p1p1_to_p3(&u, &t); ge_p3_to_cached(&r[4], &u); + ge_add(&t, &s2, &r[4]); ge_p1p1_to_p3(&u, &t); ge_p3_to_cached(&r[5], &u); + ge_add(&t, &s2, &r[5]); ge_p1p1_to_p3(&u, &t); ge_p3_to_cached(&r[6], &u); + ge_add(&t, &s2, &r[6]); ge_p1p1_to_p3(&u, &t); ge_p3_to_cached(&r[7], &u); +} + +/* +r = a * A + b * B +where a = a[0]+256*a[1]+...+256^31 a[31]. +and b = b[0]+256*b[1]+...+256^31 b[31]. +B is the Ed25519 base point (x,4/5) with x positive. +*/ + +void ge_double_scalarmult_base_vartime(ge_p2 *r, const unsigned char *a, const ge_p3 *A, const unsigned char *b) { + signed char aslide[256]; + signed char bslide[256]; + ge_dsmp Ai; /* A, 3A, 5A, 7A, 9A, 11A, 13A, 15A */ + ge_p1p1 t; + ge_p3 u; + int i; + + slide(aslide, a); + slide(bslide, b); + ge_dsm_precomp(Ai, A); + + ge_p2_0(r); + + for (i = 255; i >= 0; --i) { + if (aslide[i] || bslide[i]) break; + } + + for (; i >= 0; --i) { + ge_p2_dbl(&t, r); + + if (aslide[i] > 0) { + ge_p1p1_to_p3(&u, &t); + ge_add(&t, &u, &Ai[aslide[i]/2]); + } else if (aslide[i] < 0) { + ge_p1p1_to_p3(&u, &t); + ge_sub(&t, &u, &Ai[(-aslide[i])/2]); + } + + if (bslide[i] > 0) { + ge_p1p1_to_p3(&u, &t); + ge_madd(&t, &u, &ge_Bi[bslide[i]/2]); + } else if (bslide[i] < 0) { + ge_p1p1_to_p3(&u, &t); + ge_msub(&t, &u, &ge_Bi[(-bslide[i])/2]); + } + + ge_p1p1_to_p2(r, &t); + } +} + +// Computes aG + bB + cC (G is the fixed basepoint) +void ge_triple_scalarmult_base_vartime(ge_p2 *r, const unsigned char *a, const unsigned char *b, const ge_dsmp Bi, const unsigned char *c, const ge_dsmp Ci) { + signed char aslide[256]; + signed char bslide[256]; + signed char cslide[256]; + ge_p1p1 t; + ge_p3 u; + int i; + + slide(aslide, a); + slide(bslide, b); + slide(cslide, c); + + ge_p2_0(r); + + for (i = 255; i >= 0; --i) { + if (aslide[i] || bslide[i] || cslide[i]) break; + } + + for (; i >= 0; --i) { + ge_p2_dbl(&t, r); + + if (aslide[i] > 0) { + ge_p1p1_to_p3(&u, &t); + ge_madd(&t, &u, &ge_Bi[aslide[i]/2]); + } else if (aslide[i] < 0) { + ge_p1p1_to_p3(&u, &t); + ge_msub(&t, &u, &ge_Bi[(-aslide[i])/2]); + } + + if (bslide[i] > 0) { + ge_p1p1_to_p3(&u, &t); + ge_add(&t, &u, &Bi[bslide[i]/2]); + } else if (bslide[i] < 0) { + ge_p1p1_to_p3(&u, &t); + ge_sub(&t, &u, &Bi[(-bslide[i])/2]); + } + + if (cslide[i] > 0) { + ge_p1p1_to_p3(&u, &t); + ge_add(&t, &u, &Ci[cslide[i]/2]); + } else if (cslide[i] < 0) { + ge_p1p1_to_p3(&u, &t); + ge_sub(&t, &u, &Ci[(-cslide[i])/2]); + } + + ge_p1p1_to_p2(r, &t); + } +} + +void ge_double_scalarmult_base_vartime_p3(ge_p3 *r3, const unsigned char *a, const ge_p3 *A, const unsigned char *b) { + signed char aslide[256]; + signed char bslide[256]; + ge_dsmp Ai; /* A, 3A, 5A, 7A, 9A, 11A, 13A, 15A */ + ge_p1p1 t; + ge_p3 u; + ge_p2 r; + int i; + + slide(aslide, a); + slide(bslide, b); + ge_dsm_precomp(Ai, A); + + ge_p2_0(&r); + + for (i = 255; i >= 0; --i) { + if (aslide[i] || bslide[i]) break; + } + + for (; i >= 0; --i) { + ge_p2_dbl(&t, &r); + + if (aslide[i] > 0) { + ge_p1p1_to_p3(&u, &t); + ge_add(&t, &u, &Ai[aslide[i]/2]); + } else if (aslide[i] < 0) { + ge_p1p1_to_p3(&u, &t); + ge_sub(&t, &u, &Ai[(-aslide[i])/2]); + } + + if (bslide[i] > 0) { + ge_p1p1_to_p3(&u, &t); + ge_madd(&t, &u, &ge_Bi[bslide[i]/2]); + } else if (bslide[i] < 0) { + ge_p1p1_to_p3(&u, &t); + ge_msub(&t, &u, &ge_Bi[(-bslide[i])/2]); + } + + if (i == 0) + ge_p1p1_to_p3(r3, &t); + else + ge_p1p1_to_p2(&r, &t); + } +} + +/* From ge_frombytes.c, modified */ + +int ge_frombytes_vartime(ge_p3 *h, const unsigned char *s) { + fe u; + fe v; + fe vxx; + fe check; + + /* From fe_frombytes.c */ + + int64_t h0 = load_4(s); + int64_t h1 = load_3(s + 4) << 6; + int64_t h2 = load_3(s + 7) << 5; + int64_t h3 = load_3(s + 10) << 3; + int64_t h4 = load_3(s + 13) << 2; + int64_t h5 = load_4(s + 16); + int64_t h6 = load_3(s + 20) << 7; + int64_t h7 = load_3(s + 23) << 5; + int64_t h8 = load_3(s + 26) << 4; + int64_t h9 = (load_3(s + 29) & 8388607) << 2; + int64_t carry0; + int64_t carry1; + int64_t carry2; + int64_t carry3; + int64_t carry4; + int64_t carry5; + int64_t carry6; + int64_t carry7; + int64_t carry8; + int64_t carry9; + + /* Validate the number to be canonical */ + if (h9 == 33554428 && h8 == 268435440 && h7 == 536870880 && h6 == 2147483520 && + h5 == 4294967295 && h4 == 67108860 && h3 == 134217720 && h2 == 536870880 && + h1 == 1073741760 && h0 >= 4294967277) { + return -1; + } + + carry9 = (h9 + (int64_t) (1<<24)) >> 25; h0 += carry9 * 19; h9 -= carry9 << 25; + carry1 = (h1 + (int64_t) (1<<24)) >> 25; h2 += carry1; h1 -= carry1 << 25; + carry3 = (h3 + (int64_t) (1<<24)) >> 25; h4 += carry3; h3 -= carry3 << 25; + carry5 = (h5 + (int64_t) (1<<24)) >> 25; h6 += carry5; h5 -= carry5 << 25; + carry7 = (h7 + (int64_t) (1<<24)) >> 25; h8 += carry7; h7 -= carry7 << 25; + + carry0 = (h0 + (int64_t) (1<<25)) >> 26; h1 += carry0; h0 -= carry0 << 26; + carry2 = (h2 + (int64_t) (1<<25)) >> 26; h3 += carry2; h2 -= carry2 << 26; + carry4 = (h4 + (int64_t) (1<<25)) >> 26; h5 += carry4; h4 -= carry4 << 26; + carry6 = (h6 + (int64_t) (1<<25)) >> 26; h7 += carry6; h6 -= carry6 << 26; + carry8 = (h8 + (int64_t) (1<<25)) >> 26; h9 += carry8; h8 -= carry8 << 26; + + h->Y[0] = h0; + h->Y[1] = h1; + h->Y[2] = h2; + h->Y[3] = h3; + h->Y[4] = h4; + h->Y[5] = h5; + h->Y[6] = h6; + h->Y[7] = h7; + h->Y[8] = h8; + h->Y[9] = h9; + + /* End fe_frombytes.c */ + + fe_1(h->Z); + fe_sq(u, h->Y); + fe_mul(v, u, fe_d); + fe_sub(u, u, h->Z); /* u = y^2-1 */ + fe_add(v, v, h->Z); /* v = dy^2+1 */ + + fe_divpowm1(h->X, u, v); /* x = uv^3(uv^7)^((q-5)/8) */ + + fe_sq(vxx, h->X); + fe_mul(vxx, vxx, v); + fe_sub(check, vxx, u); /* vx^2-u */ + if (fe_isnonzero(check)) { + fe_add(check, vxx, u); /* vx^2+u */ + if (fe_isnonzero(check)) { + return -1; + } + fe_mul(h->X, h->X, fe_sqrtm1); + } + + if (fe_isnegative(h->X) != (s[31] >> 7)) { + /* If x = 0, the sign must be positive */ + if (!fe_isnonzero(h->X)) { + return -1; + } + fe_neg(h->X, h->X); + } + + fe_mul(h->T, h->X, h->Y); + return 0; +} + +/* From ge_madd.c */ + +/* +r = p + q +*/ + +static void ge_madd(ge_p1p1 *r, const ge_p3 *p, const ge_precomp *q) { + fe t0; + fe_add(r->X, p->Y, p->X); + fe_sub(r->Y, p->Y, p->X); + fe_mul(r->Z, r->X, q->yplusx); + fe_mul(r->Y, r->Y, q->yminusx); + fe_mul(r->T, q->xy2d, p->T); + fe_add(t0, p->Z, p->Z); + fe_sub(r->X, r->Z, r->Y); + fe_add(r->Y, r->Z, r->Y); + fe_add(r->Z, t0, r->T); + fe_sub(r->T, t0, r->T); +} + +/* From ge_msub.c */ + +/* +r = p - q +*/ + +static void ge_msub(ge_p1p1 *r, const ge_p3 *p, const ge_precomp *q) { + fe t0; + fe_add(r->X, p->Y, p->X); + fe_sub(r->Y, p->Y, p->X); + fe_mul(r->Z, r->X, q->yminusx); + fe_mul(r->Y, r->Y, q->yplusx); + fe_mul(r->T, q->xy2d, p->T); + fe_add(t0, p->Z, p->Z); + fe_sub(r->X, r->Z, r->Y); + fe_add(r->Y, r->Z, r->Y); + fe_sub(r->Z, t0, r->T); + fe_add(r->T, t0, r->T); +} + +/* From ge_p1p1_to_p2.c */ + +/* +r = p +*/ + +void ge_p1p1_to_p2(ge_p2 *r, const ge_p1p1 *p) { + fe_mul(r->X, p->X, p->T); + fe_mul(r->Y, p->Y, p->Z); + fe_mul(r->Z, p->Z, p->T); +} + +/* From ge_p1p1_to_p3.c */ + +/* +r = p +*/ + +void ge_p1p1_to_p3(ge_p3 *r, const ge_p1p1 *p) { + fe_mul(r->X, p->X, p->T); + fe_mul(r->Y, p->Y, p->Z); + fe_mul(r->Z, p->Z, p->T); + fe_mul(r->T, p->X, p->Y); +} + +/* From ge_p2_0.c */ + +static void ge_p2_0(ge_p2 *h) { + fe_0(h->X); + fe_1(h->Y); + fe_1(h->Z); +} + +/* From ge_p2_dbl.c */ + +/* +r = 2 * p +*/ + +void ge_p2_dbl(ge_p1p1 *r, const ge_p2 *p) { + fe t0; + fe_sq(r->X, p->X); + fe_sq(r->Z, p->Y); + fe_sq2(r->T, p->Z); + fe_add(r->Y, p->X, p->Y); + fe_sq(t0, r->Y); + fe_add(r->Y, r->Z, r->X); + fe_sub(r->Z, r->Z, r->X); + fe_sub(r->X, t0, r->Y); + fe_sub(r->T, r->T, r->Z); +} + +/* From ge_p3_0.c */ + +static void ge_p3_0(ge_p3 *h) { + fe_0(h->X); + fe_1(h->Y); + fe_1(h->Z); + fe_0(h->T); +} + +/* From ge_p3_dbl.c */ + +/* +r = 2 * p +*/ + +static void ge_p3_dbl(ge_p1p1 *r, const ge_p3 *p) { + ge_p2 q; + ge_p3_to_p2(&q, p); + ge_p2_dbl(r, &q); +} + +/* From ge_p3_to_cached.c */ + +/* +r = p +*/ + +void ge_p3_to_cached(ge_cached *r, const ge_p3 *p) { + fe_add(r->YplusX, p->Y, p->X); + fe_sub(r->YminusX, p->Y, p->X); + fe_copy(r->Z, p->Z); + fe_mul(r->T2d, p->T, fe_d2); +} + +/* From ge_p3_to_p2.c */ + +/* +r = p +*/ + +void ge_p3_to_p2(ge_p2 *r, const ge_p3 *p) { + fe_copy(r->X, p->X); + fe_copy(r->Y, p->Y); + fe_copy(r->Z, p->Z); +} + +/* From ge_p3_tobytes.c */ + +void ge_p3_tobytes(unsigned char *s, const ge_p3 *h) { + fe recip; + fe x; + fe y; + + fe_invert(recip, h->Z); + fe_mul(x, h->X, recip); + fe_mul(y, h->Y, recip); + fe_tobytes(s, y); + s[31] ^= fe_isnegative(x) << 7; +} + +/* From ge_precomp_0.c */ + +static void ge_precomp_0(ge_precomp *h) { + fe_1(h->yplusx); + fe_1(h->yminusx); + fe_0(h->xy2d); +} + +/* From ge_scalarmult_base.c */ + +static unsigned char equal(signed char b, signed char c) { + unsigned char ub = b; + unsigned char uc = c; + unsigned char x = ub ^ uc; /* 0: yes; 1..255: no */ + uint32_t y = x; /* 0: yes; 1..255: no */ + y -= 1; /* 4294967295: yes; 0..254: no */ + y >>= 31; /* 1: yes; 0: no */ + return y; +} + +static unsigned char negative(signed char b) { + unsigned long long x = b; /* 18446744073709551361..18446744073709551615: yes; 0..255: no */ + x >>= 63; /* 1: yes; 0: no */ + return x; +} + +static void ge_precomp_cmov(ge_precomp *t, const ge_precomp *u, unsigned char b) { + fe_cmov(t->yplusx, u->yplusx, b); + fe_cmov(t->yminusx, u->yminusx, b); + fe_cmov(t->xy2d, u->xy2d, b); +} + +static void select(ge_precomp *t, int pos, signed char b) { + ge_precomp minust; + unsigned char bnegative = negative(b); + unsigned char babs = b - (((-bnegative) & b) << 1); + + ge_precomp_0(t); + ge_precomp_cmov(t, &ge_base[pos][0], equal(babs, 1)); + ge_precomp_cmov(t, &ge_base[pos][1], equal(babs, 2)); + ge_precomp_cmov(t, &ge_base[pos][2], equal(babs, 3)); + ge_precomp_cmov(t, &ge_base[pos][3], equal(babs, 4)); + ge_precomp_cmov(t, &ge_base[pos][4], equal(babs, 5)); + ge_precomp_cmov(t, &ge_base[pos][5], equal(babs, 6)); + ge_precomp_cmov(t, &ge_base[pos][6], equal(babs, 7)); + ge_precomp_cmov(t, &ge_base[pos][7], equal(babs, 8)); + fe_copy(minust.yplusx, t->yminusx); + fe_copy(minust.yminusx, t->yplusx); + fe_neg(minust.xy2d, t->xy2d); + ge_precomp_cmov(t, &minust, bnegative); +} + +/* +h = a * B +where a = a[0]+256*a[1]+...+256^31 a[31] +B is the Ed25519 base point (x,4/5) with x positive. + +Preconditions: + a[31] <= 127 +*/ + +void ge_scalarmult_base(ge_p3 *h, const unsigned char *a) { + signed char e[64]; + signed char carry; + ge_p1p1 r; + ge_p2 s; + ge_precomp t; + int i; + + for (i = 0; i < 32; ++i) { + e[2 * i + 0] = (a[i] >> 0) & 15; + e[2 * i + 1] = (a[i] >> 4) & 15; + } + /* each e[i] is between 0 and 15 */ + /* e[63] is between 0 and 7 */ + + carry = 0; + for (i = 0; i < 63; ++i) { + e[i] += carry; + carry = e[i] + 8; + carry >>= 4; + e[i] -= carry << 4; + } + e[63] += carry; + /* each e[i] is between -8 and 8 */ + + ge_p3_0(h); + for (i = 1; i < 64; i += 2) { + select(&t, i / 2, e[i]); + ge_madd(&r, h, &t); ge_p1p1_to_p3(h, &r); + } + + ge_p3_dbl(&r, h); ge_p1p1_to_p2(&s, &r); + ge_p2_dbl(&r, &s); ge_p1p1_to_p2(&s, &r); + ge_p2_dbl(&r, &s); ge_p1p1_to_p2(&s, &r); + ge_p2_dbl(&r, &s); ge_p1p1_to_p3(h, &r); + + for (i = 0; i < 64; i += 2) { + select(&t, i / 2, e[i]); + ge_madd(&r, h, &t); ge_p1p1_to_p3(h, &r); + } +} + +/* From ge_sub.c */ + +/* +r = p - q +*/ + +void ge_sub(ge_p1p1 *r, const ge_p3 *p, const ge_cached *q) { + fe t0; + fe_add(r->X, p->Y, p->X); + fe_sub(r->Y, p->Y, p->X); + fe_mul(r->Z, r->X, q->YminusX); + fe_mul(r->Y, r->Y, q->YplusX); + fe_mul(r->T, q->T2d, p->T); + fe_mul(r->X, p->Z, q->Z); + fe_add(t0, r->X, r->X); + fe_sub(r->X, r->Z, r->Y); + fe_add(r->Y, r->Z, r->Y); + fe_sub(r->Z, t0, r->T); + fe_add(r->T, t0, r->T); +} + +/* From ge_tobytes.c */ + +void ge_tobytes(unsigned char *s, const ge_p2 *h) { + fe recip; + fe x; + fe y; + + fe_invert(recip, h->Z); + fe_mul(x, h->X, recip); + fe_mul(y, h->Y, recip); + fe_tobytes(s, y); + s[31] ^= fe_isnegative(x) << 7; +} + +/* From sc_reduce.c */ + +/* +Input: + s[0]+256*s[1]+...+256^63*s[63] = s + +Output: + s[0]+256*s[1]+...+256^31*s[31] = s mod l + where l = 2^252 + 27742317777372353535851937790883648493. + Overwrites s in place. +*/ + +void sc_reduce(unsigned char *s) { + int64_t s0 = 2097151 & load_3(s); + int64_t s1 = 2097151 & (load_4(s + 2) >> 5); + int64_t s2 = 2097151 & (load_3(s + 5) >> 2); + int64_t s3 = 2097151 & (load_4(s + 7) >> 7); + int64_t s4 = 2097151 & (load_4(s + 10) >> 4); + int64_t s5 = 2097151 & (load_3(s + 13) >> 1); + int64_t s6 = 2097151 & (load_4(s + 15) >> 6); + int64_t s7 = 2097151 & (load_3(s + 18) >> 3); + int64_t s8 = 2097151 & load_3(s + 21); + int64_t s9 = 2097151 & (load_4(s + 23) >> 5); + int64_t s10 = 2097151 & (load_3(s + 26) >> 2); + int64_t s11 = 2097151 & (load_4(s + 28) >> 7); + int64_t s12 = 2097151 & (load_4(s + 31) >> 4); + int64_t s13 = 2097151 & (load_3(s + 34) >> 1); + int64_t s14 = 2097151 & (load_4(s + 36) >> 6); + int64_t s15 = 2097151 & (load_3(s + 39) >> 3); + int64_t s16 = 2097151 & load_3(s + 42); + int64_t s17 = 2097151 & (load_4(s + 44) >> 5); + int64_t s18 = 2097151 & (load_3(s + 47) >> 2); + int64_t s19 = 2097151 & (load_4(s + 49) >> 7); + int64_t s20 = 2097151 & (load_4(s + 52) >> 4); + int64_t s21 = 2097151 & (load_3(s + 55) >> 1); + int64_t s22 = 2097151 & (load_4(s + 57) >> 6); + int64_t s23 = (load_4(s + 60) >> 3); + int64_t carry0; + int64_t carry1; + int64_t carry2; + int64_t carry3; + int64_t carry4; + int64_t carry5; + int64_t carry6; + int64_t carry7; + int64_t carry8; + int64_t carry9; + int64_t carry10; + int64_t carry11; + int64_t carry12; + int64_t carry13; + int64_t carry14; + int64_t carry15; + int64_t carry16; + + s11 += s23 * 666643; + s12 += s23 * 470296; + s13 += s23 * 654183; + s14 -= s23 * 997805; + s15 += s23 * 136657; + s16 -= s23 * 683901; + + s10 += s22 * 666643; + s11 += s22 * 470296; + s12 += s22 * 654183; + s13 -= s22 * 997805; + s14 += s22 * 136657; + s15 -= s22 * 683901; + + s9 += s21 * 666643; + s10 += s21 * 470296; + s11 += s21 * 654183; + s12 -= s21 * 997805; + s13 += s21 * 136657; + s14 -= s21 * 683901; + + s8 += s20 * 666643; + s9 += s20 * 470296; + s10 += s20 * 654183; + s11 -= s20 * 997805; + s12 += s20 * 136657; + s13 -= s20 * 683901; + + s7 += s19 * 666643; + s8 += s19 * 470296; + s9 += s19 * 654183; + s10 -= s19 * 997805; + s11 += s19 * 136657; + s12 -= s19 * 683901; + + s6 += s18 * 666643; + s7 += s18 * 470296; + s8 += s18 * 654183; + s9 -= s18 * 997805; + s10 += s18 * 136657; + s11 -= s18 * 683901; + + carry6 = (s6 + (1<<20)) >> 21; s7 += carry6; s6 -= carry6 << 21; + carry8 = (s8 + (1<<20)) >> 21; s9 += carry8; s8 -= carry8 << 21; + carry10 = (s10 + (1<<20)) >> 21; s11 += carry10; s10 -= carry10 << 21; + carry12 = (s12 + (1<<20)) >> 21; s13 += carry12; s12 -= carry12 << 21; + carry14 = (s14 + (1<<20)) >> 21; s15 += carry14; s14 -= carry14 << 21; + carry16 = (s16 + (1<<20)) >> 21; s17 += carry16; s16 -= carry16 << 21; + + carry7 = (s7 + (1<<20)) >> 21; s8 += carry7; s7 -= carry7 << 21; + carry9 = (s9 + (1<<20)) >> 21; s10 += carry9; s9 -= carry9 << 21; + carry11 = (s11 + (1<<20)) >> 21; s12 += carry11; s11 -= carry11 << 21; + carry13 = (s13 + (1<<20)) >> 21; s14 += carry13; s13 -= carry13 << 21; + carry15 = (s15 + (1<<20)) >> 21; s16 += carry15; s15 -= carry15 << 21; + + s5 += s17 * 666643; + s6 += s17 * 470296; + s7 += s17 * 654183; + s8 -= s17 * 997805; + s9 += s17 * 136657; + s10 -= s17 * 683901; + + s4 += s16 * 666643; + s5 += s16 * 470296; + s6 += s16 * 654183; + s7 -= s16 * 997805; + s8 += s16 * 136657; + s9 -= s16 * 683901; + + s3 += s15 * 666643; + s4 += s15 * 470296; + s5 += s15 * 654183; + s6 -= s15 * 997805; + s7 += s15 * 136657; + s8 -= s15 * 683901; + + s2 += s14 * 666643; + s3 += s14 * 470296; + s4 += s14 * 654183; + s5 -= s14 * 997805; + s6 += s14 * 136657; + s7 -= s14 * 683901; + + s1 += s13 * 666643; + s2 += s13 * 470296; + s3 += s13 * 654183; + s4 -= s13 * 997805; + s5 += s13 * 136657; + s6 -= s13 * 683901; + + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + s12 = 0; + + carry0 = (s0 + (1<<20)) >> 21; s1 += carry0; s0 -= carry0 << 21; + carry2 = (s2 + (1<<20)) >> 21; s3 += carry2; s2 -= carry2 << 21; + carry4 = (s4 + (1<<20)) >> 21; s5 += carry4; s4 -= carry4 << 21; + carry6 = (s6 + (1<<20)) >> 21; s7 += carry6; s6 -= carry6 << 21; + carry8 = (s8 + (1<<20)) >> 21; s9 += carry8; s8 -= carry8 << 21; + carry10 = (s10 + (1<<20)) >> 21; s11 += carry10; s10 -= carry10 << 21; + + carry1 = (s1 + (1<<20)) >> 21; s2 += carry1; s1 -= carry1 << 21; + carry3 = (s3 + (1<<20)) >> 21; s4 += carry3; s3 -= carry3 << 21; + carry5 = (s5 + (1<<20)) >> 21; s6 += carry5; s5 -= carry5 << 21; + carry7 = (s7 + (1<<20)) >> 21; s8 += carry7; s7 -= carry7 << 21; + carry9 = (s9 + (1<<20)) >> 21; s10 += carry9; s9 -= carry9 << 21; + carry11 = (s11 + (1<<20)) >> 21; s12 += carry11; s11 -= carry11 << 21; + + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + s12 = 0; + + carry0 = s0 >> 21; s1 += carry0; s0 -= carry0 << 21; + carry1 = s1 >> 21; s2 += carry1; s1 -= carry1 << 21; + carry2 = s2 >> 21; s3 += carry2; s2 -= carry2 << 21; + carry3 = s3 >> 21; s4 += carry3; s3 -= carry3 << 21; + carry4 = s4 >> 21; s5 += carry4; s4 -= carry4 << 21; + carry5 = s5 >> 21; s6 += carry5; s5 -= carry5 << 21; + carry6 = s6 >> 21; s7 += carry6; s6 -= carry6 << 21; + carry7 = s7 >> 21; s8 += carry7; s7 -= carry7 << 21; + carry8 = s8 >> 21; s9 += carry8; s8 -= carry8 << 21; + carry9 = s9 >> 21; s10 += carry9; s9 -= carry9 << 21; + carry10 = s10 >> 21; s11 += carry10; s10 -= carry10 << 21; + carry11 = s11 >> 21; s12 += carry11; s11 -= carry11 << 21; + + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + + carry0 = s0 >> 21; s1 += carry0; s0 -= carry0 << 21; + carry1 = s1 >> 21; s2 += carry1; s1 -= carry1 << 21; + carry2 = s2 >> 21; s3 += carry2; s2 -= carry2 << 21; + carry3 = s3 >> 21; s4 += carry3; s3 -= carry3 << 21; + carry4 = s4 >> 21; s5 += carry4; s4 -= carry4 << 21; + carry5 = s5 >> 21; s6 += carry5; s5 -= carry5 << 21; + carry6 = s6 >> 21; s7 += carry6; s6 -= carry6 << 21; + carry7 = s7 >> 21; s8 += carry7; s7 -= carry7 << 21; + carry8 = s8 >> 21; s9 += carry8; s8 -= carry8 << 21; + carry9 = s9 >> 21; s10 += carry9; s9 -= carry9 << 21; + carry10 = s10 >> 21; s11 += carry10; s10 -= carry10 << 21; + + s[0] = s0 >> 0; + s[1] = s0 >> 8; + s[2] = (s0 >> 16) | (s1 << 5); + s[3] = s1 >> 3; + s[4] = s1 >> 11; + s[5] = (s1 >> 19) | (s2 << 2); + s[6] = s2 >> 6; + s[7] = (s2 >> 14) | (s3 << 7); + s[8] = s3 >> 1; + s[9] = s3 >> 9; + s[10] = (s3 >> 17) | (s4 << 4); + s[11] = s4 >> 4; + s[12] = s4 >> 12; + s[13] = (s4 >> 20) | (s5 << 1); + s[14] = s5 >> 7; + s[15] = (s5 >> 15) | (s6 << 6); + s[16] = s6 >> 2; + s[17] = s6 >> 10; + s[18] = (s6 >> 18) | (s7 << 3); + s[19] = s7 >> 5; + s[20] = s7 >> 13; + s[21] = s8 >> 0; + s[22] = s8 >> 8; + s[23] = (s8 >> 16) | (s9 << 5); + s[24] = s9 >> 3; + s[25] = s9 >> 11; + s[26] = (s9 >> 19) | (s10 << 2); + s[27] = s10 >> 6; + s[28] = (s10 >> 14) | (s11 << 7); + s[29] = s11 >> 1; + s[30] = s11 >> 9; + s[31] = s11 >> 17; +} + +/* New code */ + +static void fe_divpowm1(fe r, const fe u, const fe v) { + fe v3, uv7, t0, t1, t2; + int i; + + fe_sq(v3, v); + fe_mul(v3, v3, v); /* v3 = v^3 */ + fe_sq(uv7, v3); + fe_mul(uv7, uv7, v); + fe_mul(uv7, uv7, u); /* uv7 = uv^7 */ + + /*fe_pow22523(uv7, uv7);*/ + + /* From fe_pow22523.c */ + + fe_sq(t0, uv7); + fe_sq(t1, t0); + fe_sq(t1, t1); + fe_mul(t1, uv7, t1); + fe_mul(t0, t0, t1); + fe_sq(t0, t0); + fe_mul(t0, t1, t0); + fe_sq(t1, t0); + for (i = 0; i < 4; ++i) { + fe_sq(t1, t1); + } + fe_mul(t0, t1, t0); + fe_sq(t1, t0); + for (i = 0; i < 9; ++i) { + fe_sq(t1, t1); + } + fe_mul(t1, t1, t0); + fe_sq(t2, t1); + for (i = 0; i < 19; ++i) { + fe_sq(t2, t2); + } + fe_mul(t1, t2, t1); + for (i = 0; i < 10; ++i) { + fe_sq(t1, t1); + } + fe_mul(t0, t1, t0); + fe_sq(t1, t0); + for (i = 0; i < 49; ++i) { + fe_sq(t1, t1); + } + fe_mul(t1, t1, t0); + fe_sq(t2, t1); + for (i = 0; i < 99; ++i) { + fe_sq(t2, t2); + } + fe_mul(t1, t2, t1); + for (i = 0; i < 50; ++i) { + fe_sq(t1, t1); + } + fe_mul(t0, t1, t0); + fe_sq(t0, t0); + fe_sq(t0, t0); + fe_mul(t0, t0, uv7); + + /* End fe_pow22523.c */ + /* t0 = (uv^7)^((q-5)/8) */ + fe_mul(t0, t0, v3); + fe_mul(r, t0, u); /* u^(m+1)v^(-(m+1)) */ +} + +static void ge_cached_0(ge_cached *r) { + fe_1(r->YplusX); + fe_1(r->YminusX); + fe_1(r->Z); + fe_0(r->T2d); +} + +static void ge_cached_cmov(ge_cached *t, const ge_cached *u, unsigned char b) { + fe_cmov(t->YplusX, u->YplusX, b); + fe_cmov(t->YminusX, u->YminusX, b); + fe_cmov(t->Z, u->Z, b); + fe_cmov(t->T2d, u->T2d, b); +} + +/* Assumes that a[31] <= 127 */ +void ge_scalarmult(ge_p2 *r, const unsigned char *a, const ge_p3 *A) { + signed char e[64]; + int carry, carry2, i; + ge_cached Ai[8]; /* 1 * A, 2 * A, ..., 8 * A */ + ge_p1p1 t; + ge_p3 u; + + carry = 0; /* 0..1 */ + for (i = 0; i < 31; i++) { + carry += a[i]; /* 0..256 */ + carry2 = (carry + 8) >> 4; /* 0..16 */ + e[2 * i] = carry - (carry2 << 4); /* -8..7 */ + carry = (carry2 + 8) >> 4; /* 0..1 */ + e[2 * i + 1] = carry2 - (carry << 4); /* -8..7 */ + } + carry += a[31]; /* 0..128 */ + carry2 = (carry + 8) >> 4; /* 0..8 */ + e[62] = carry - (carry2 << 4); /* -8..7 */ + e[63] = carry2; /* 0..8 */ + + ge_p3_to_cached(&Ai[0], A); + for (i = 0; i < 7; i++) { + ge_add(&t, A, &Ai[i]); + ge_p1p1_to_p3(&u, &t); + ge_p3_to_cached(&Ai[i + 1], &u); + } + + ge_p2_0(r); + for (i = 63; i >= 0; i--) { + signed char b = e[i]; + unsigned char bnegative = negative(b); + unsigned char babs = b - (((-bnegative) & b) << 1); + ge_cached cur, minuscur; + ge_p2_dbl(&t, r); + ge_p1p1_to_p2(r, &t); + ge_p2_dbl(&t, r); + ge_p1p1_to_p2(r, &t); + ge_p2_dbl(&t, r); + ge_p1p1_to_p2(r, &t); + ge_p2_dbl(&t, r); + ge_p1p1_to_p3(&u, &t); + ge_cached_0(&cur); + ge_cached_cmov(&cur, &Ai[0], equal(babs, 1)); + ge_cached_cmov(&cur, &Ai[1], equal(babs, 2)); + ge_cached_cmov(&cur, &Ai[2], equal(babs, 3)); + ge_cached_cmov(&cur, &Ai[3], equal(babs, 4)); + ge_cached_cmov(&cur, &Ai[4], equal(babs, 5)); + ge_cached_cmov(&cur, &Ai[5], equal(babs, 6)); + ge_cached_cmov(&cur, &Ai[6], equal(babs, 7)); + ge_cached_cmov(&cur, &Ai[7], equal(babs, 8)); + fe_copy(minuscur.YplusX, cur.YminusX); + fe_copy(minuscur.YminusX, cur.YplusX); + fe_copy(minuscur.Z, cur.Z); + fe_neg(minuscur.T2d, cur.T2d); + ge_cached_cmov(&cur, &minuscur, bnegative); + ge_add(&t, &u, &cur); + ge_p1p1_to_p2(r, &t); + } +} + +void ge_scalarmult_p3(ge_p3 *r3, const unsigned char *a, const ge_p3 *A) { + signed char e[64]; + int carry, carry2, i; + ge_cached Ai[8]; /* 1 * A, 2 * A, ..., 8 * A */ + ge_p1p1 t; + ge_p3 u; + ge_p2 r; + + carry = 0; /* 0..1 */ + for (i = 0; i < 31; i++) { + carry += a[i]; /* 0..256 */ + carry2 = (carry + 8) >> 4; /* 0..16 */ + e[2 * i] = carry - (carry2 << 4); /* -8..7 */ + carry = (carry2 + 8) >> 4; /* 0..1 */ + e[2 * i + 1] = carry2 - (carry << 4); /* -8..7 */ + } + carry += a[31]; /* 0..128 */ + carry2 = (carry + 8) >> 4; /* 0..8 */ + e[62] = carry - (carry2 << 4); /* -8..7 */ + e[63] = carry2; /* 0..8 */ + + ge_p3_to_cached(&Ai[0], A); + for (i = 0; i < 7; i++) { + ge_add(&t, A, &Ai[i]); + ge_p1p1_to_p3(&u, &t); + ge_p3_to_cached(&Ai[i + 1], &u); + } + + ge_p2_0(&r); + for (i = 63; i >= 0; i--) { + signed char b = e[i]; + unsigned char bnegative = negative(b); + unsigned char babs = b - (((-bnegative) & b) << 1); + ge_cached cur, minuscur; + ge_p2_dbl(&t, &r); + ge_p1p1_to_p2(&r, &t); + ge_p2_dbl(&t, &r); + ge_p1p1_to_p2(&r, &t); + ge_p2_dbl(&t, &r); + ge_p1p1_to_p2(&r, &t); + ge_p2_dbl(&t, &r); + ge_p1p1_to_p3(&u, &t); + ge_cached_0(&cur); + ge_cached_cmov(&cur, &Ai[0], equal(babs, 1)); + ge_cached_cmov(&cur, &Ai[1], equal(babs, 2)); + ge_cached_cmov(&cur, &Ai[2], equal(babs, 3)); + ge_cached_cmov(&cur, &Ai[3], equal(babs, 4)); + ge_cached_cmov(&cur, &Ai[4], equal(babs, 5)); + ge_cached_cmov(&cur, &Ai[5], equal(babs, 6)); + ge_cached_cmov(&cur, &Ai[6], equal(babs, 7)); + ge_cached_cmov(&cur, &Ai[7], equal(babs, 8)); + fe_copy(minuscur.YplusX, cur.YminusX); + fe_copy(minuscur.YminusX, cur.YplusX); + fe_copy(minuscur.Z, cur.Z); + fe_neg(minuscur.T2d, cur.T2d); + ge_cached_cmov(&cur, &minuscur, bnegative); + ge_add(&t, &u, &cur); + if (i == 0) + ge_p1p1_to_p3(r3, &t); + else + ge_p1p1_to_p2(&r, &t); + } +} + +void ge_double_scalarmult_precomp_vartime2(ge_p2 *r, const unsigned char *a, const ge_dsmp Ai, const unsigned char *b, const ge_dsmp Bi) { + signed char aslide[256]; + signed char bslide[256]; + ge_p1p1 t; + ge_p3 u; + int i; + + slide(aslide, a); + slide(bslide, b); + + ge_p2_0(r); + + for (i = 255; i >= 0; --i) { + if (aslide[i] || bslide[i]) break; + } + + for (; i >= 0; --i) { + ge_p2_dbl(&t, r); + + if (aslide[i] > 0) { + ge_p1p1_to_p3(&u, &t); + ge_add(&t, &u, &Ai[aslide[i]/2]); + } else if (aslide[i] < 0) { + ge_p1p1_to_p3(&u, &t); + ge_sub(&t, &u, &Ai[(-aslide[i])/2]); + } + + if (bslide[i] > 0) { + ge_p1p1_to_p3(&u, &t); + ge_add(&t, &u, &Bi[bslide[i]/2]); + } else if (bslide[i] < 0) { + ge_p1p1_to_p3(&u, &t); + ge_sub(&t, &u, &Bi[(-bslide[i])/2]); + } + + ge_p1p1_to_p2(r, &t); + } +} + +// Computes aA + bB + cC (all points require precomputation) +void ge_triple_scalarmult_precomp_vartime(ge_p2 *r, const unsigned char *a, const ge_dsmp Ai, const unsigned char *b, const ge_dsmp Bi, const unsigned char *c, const ge_dsmp Ci) { + signed char aslide[256]; + signed char bslide[256]; + signed char cslide[256]; + ge_p1p1 t; + ge_p3 u; + int i; + + slide(aslide, a); + slide(bslide, b); + slide(cslide, c); + + ge_p2_0(r); + + for (i = 255; i >= 0; --i) { + if (aslide[i] || bslide[i] || cslide[i]) break; + } + + for (; i >= 0; --i) { + ge_p2_dbl(&t, r); + + if (aslide[i] > 0) { + ge_p1p1_to_p3(&u, &t); + ge_add(&t, &u, &Ai[aslide[i]/2]); + } else if (aslide[i] < 0) { + ge_p1p1_to_p3(&u, &t); + ge_sub(&t, &u, &Ai[(-aslide[i])/2]); + } + + if (bslide[i] > 0) { + ge_p1p1_to_p3(&u, &t); + ge_add(&t, &u, &Bi[bslide[i]/2]); + } else if (bslide[i] < 0) { + ge_p1p1_to_p3(&u, &t); + ge_sub(&t, &u, &Bi[(-bslide[i])/2]); + } + + if (cslide[i] > 0) { + ge_p1p1_to_p3(&u, &t); + ge_add(&t, &u, &Ci[cslide[i]/2]); + } else if (cslide[i] < 0) { + ge_p1p1_to_p3(&u, &t); + ge_sub(&t, &u, &Ci[(-cslide[i])/2]); + } + + ge_p1p1_to_p2(r, &t); + } +} + +void ge_double_scalarmult_precomp_vartime2_p3(ge_p3 *r3, const unsigned char *a, const ge_dsmp Ai, const unsigned char *b, const ge_dsmp Bi) { + signed char aslide[256]; + signed char bslide[256]; + ge_p1p1 t; + ge_p3 u; + ge_p2 r; + int i; + + slide(aslide, a); + slide(bslide, b); + + ge_p2_0(&r); + + for (i = 255; i >= 0; --i) { + if (aslide[i] || bslide[i]) break; + } + + for (; i >= 0; --i) { + ge_p2_dbl(&t, &r); + + if (aslide[i] > 0) { + ge_p1p1_to_p3(&u, &t); + ge_add(&t, &u, &Ai[aslide[i]/2]); + } else if (aslide[i] < 0) { + ge_p1p1_to_p3(&u, &t); + ge_sub(&t, &u, &Ai[(-aslide[i])/2]); + } + + if (bslide[i] > 0) { + ge_p1p1_to_p3(&u, &t); + ge_add(&t, &u, &Bi[bslide[i]/2]); + } else if (bslide[i] < 0) { + ge_p1p1_to_p3(&u, &t); + ge_sub(&t, &u, &Bi[(-bslide[i])/2]); + } + + if (i == 0) + ge_p1p1_to_p3(r3, &t); + else + ge_p1p1_to_p2(&r, &t); + } +} + +void ge_double_scalarmult_precomp_vartime(ge_p2 *r, const unsigned char *a, const ge_p3 *A, const unsigned char *b, const ge_dsmp Bi) { + ge_dsmp Ai; /* A, 3A, 5A, 7A, 9A, 11A, 13A, 15A */ + + ge_dsm_precomp(Ai, A); + ge_double_scalarmult_precomp_vartime2(r, a, Ai, b, Bi); +} + +void ge_mul8(ge_p1p1 *r, const ge_p2 *t) { + ge_p2 u; + ge_p2_dbl(r, t); + ge_p1p1_to_p2(&u, r); + ge_p2_dbl(r, &u); + ge_p1p1_to_p2(&u, r); + ge_p2_dbl(r, &u); +} + +void ge_fromfe_frombytes_vartime(ge_p2 *r, const unsigned char *s) { + fe u, v, w, x, y, z; + unsigned char sign; + + /* From fe_frombytes.c */ + + int64_t h0 = load_4(s); + int64_t h1 = load_3(s + 4) << 6; + int64_t h2 = load_3(s + 7) << 5; + int64_t h3 = load_3(s + 10) << 3; + int64_t h4 = load_3(s + 13) << 2; + int64_t h5 = load_4(s + 16); + int64_t h6 = load_3(s + 20) << 7; + int64_t h7 = load_3(s + 23) << 5; + int64_t h8 = load_3(s + 26) << 4; + int64_t h9 = load_3(s + 29) << 2; + int64_t carry0; + int64_t carry1; + int64_t carry2; + int64_t carry3; + int64_t carry4; + int64_t carry5; + int64_t carry6; + int64_t carry7; + int64_t carry8; + int64_t carry9; + + carry9 = (h9 + (int64_t) (1<<24)) >> 25; h0 += carry9 * 19; h9 -= carry9 << 25; + carry1 = (h1 + (int64_t) (1<<24)) >> 25; h2 += carry1; h1 -= carry1 << 25; + carry3 = (h3 + (int64_t) (1<<24)) >> 25; h4 += carry3; h3 -= carry3 << 25; + carry5 = (h5 + (int64_t) (1<<24)) >> 25; h6 += carry5; h5 -= carry5 << 25; + carry7 = (h7 + (int64_t) (1<<24)) >> 25; h8 += carry7; h7 -= carry7 << 25; + + carry0 = (h0 + (int64_t) (1<<25)) >> 26; h1 += carry0; h0 -= carry0 << 26; + carry2 = (h2 + (int64_t) (1<<25)) >> 26; h3 += carry2; h2 -= carry2 << 26; + carry4 = (h4 + (int64_t) (1<<25)) >> 26; h5 += carry4; h4 -= carry4 << 26; + carry6 = (h6 + (int64_t) (1<<25)) >> 26; h7 += carry6; h6 -= carry6 << 26; + carry8 = (h8 + (int64_t) (1<<25)) >> 26; h9 += carry8; h8 -= carry8 << 26; + + u[0] = h0; + u[1] = h1; + u[2] = h2; + u[3] = h3; + u[4] = h4; + u[5] = h5; + u[6] = h6; + u[7] = h7; + u[8] = h8; + u[9] = h9; + + /* End fe_frombytes.c */ + + fe_sq2(v, u); /* 2 * u^2 */ + fe_1(w); + fe_add(w, v, w); /* w = 2 * u^2 + 1 */ + fe_sq(x, w); /* w^2 */ + fe_mul(y, fe_ma2, v); /* -2 * A^2 * u^2 */ + fe_add(x, x, y); /* x = w^2 - 2 * A^2 * u^2 */ + fe_divpowm1(r->X, w, x); /* (w / x)^(m + 1) */ + fe_sq(y, r->X); + fe_mul(x, y, x); + fe_sub(y, w, x); + fe_copy(z, fe_ma); + if (fe_isnonzero(y)) { + fe_add(y, w, x); + if (fe_isnonzero(y)) { + goto negative; + } else { + fe_mul(r->X, r->X, fe_fffb1); + } + } else { + fe_mul(r->X, r->X, fe_fffb2); + } + fe_mul(r->X, r->X, u); /* u * sqrt(2 * A * (A + 2) * w / x) */ + fe_mul(z, z, v); /* -2 * A * u^2 */ + sign = 0; + goto setsign; +negative: + fe_mul(x, x, fe_sqrtm1); + fe_sub(y, w, x); + if (fe_isnonzero(y)) { + assert((fe_add(y, w, x), !fe_isnonzero(y))); + fe_mul(r->X, r->X, fe_fffb3); + } else { + fe_mul(r->X, r->X, fe_fffb4); + } + /* r->X = sqrt(A * (A + 2) * w / x) */ + /* z = -A */ + sign = 1; +setsign: + if (fe_isnegative(r->X) != sign) { + assert(fe_isnonzero(r->X)); + fe_neg(r->X, r->X); + } + fe_add(r->Z, z, w); + fe_sub(r->Y, z, w); + fe_mul(r->X, r->X, r->Z); +#if !defined(NDEBUG) + { + fe check_x, check_y, check_iz, check_v; + fe_invert(check_iz, r->Z); + fe_mul(check_x, r->X, check_iz); + fe_mul(check_y, r->Y, check_iz); + fe_sq(check_x, check_x); + fe_sq(check_y, check_y); + fe_mul(check_v, check_x, check_y); + fe_mul(check_v, fe_d, check_v); + fe_add(check_v, check_v, check_x); + fe_sub(check_v, check_v, check_y); + fe_1(check_x); + fe_add(check_v, check_v, check_x); + assert(!fe_isnonzero(check_v)); + } +#endif +} + +void sc_0(unsigned char *s) { + int i; + for (i = 0; i < 32; i++) { + s[i] = 0; + } +} + +void sc_reduce32(unsigned char *s) { + int64_t s0 = 2097151 & load_3(s); + int64_t s1 = 2097151 & (load_4(s + 2) >> 5); + int64_t s2 = 2097151 & (load_3(s + 5) >> 2); + int64_t s3 = 2097151 & (load_4(s + 7) >> 7); + int64_t s4 = 2097151 & (load_4(s + 10) >> 4); + int64_t s5 = 2097151 & (load_3(s + 13) >> 1); + int64_t s6 = 2097151 & (load_4(s + 15) >> 6); + int64_t s7 = 2097151 & (load_3(s + 18) >> 3); + int64_t s8 = 2097151 & load_3(s + 21); + int64_t s9 = 2097151 & (load_4(s + 23) >> 5); + int64_t s10 = 2097151 & (load_3(s + 26) >> 2); + int64_t s11 = (load_4(s + 28) >> 7); + int64_t s12 = 0; + int64_t carry0; + int64_t carry1; + int64_t carry2; + int64_t carry3; + int64_t carry4; + int64_t carry5; + int64_t carry6; + int64_t carry7; + int64_t carry8; + int64_t carry9; + int64_t carry10; + int64_t carry11; + + carry0 = (s0 + (1<<20)) >> 21; s1 += carry0; s0 -= carry0 << 21; + carry2 = (s2 + (1<<20)) >> 21; s3 += carry2; s2 -= carry2 << 21; + carry4 = (s4 + (1<<20)) >> 21; s5 += carry4; s4 -= carry4 << 21; + carry6 = (s6 + (1<<20)) >> 21; s7 += carry6; s6 -= carry6 << 21; + carry8 = (s8 + (1<<20)) >> 21; s9 += carry8; s8 -= carry8 << 21; + carry10 = (s10 + (1<<20)) >> 21; s11 += carry10; s10 -= carry10 << 21; + + carry1 = (s1 + (1<<20)) >> 21; s2 += carry1; s1 -= carry1 << 21; + carry3 = (s3 + (1<<20)) >> 21; s4 += carry3; s3 -= carry3 << 21; + carry5 = (s5 + (1<<20)) >> 21; s6 += carry5; s5 -= carry5 << 21; + carry7 = (s7 + (1<<20)) >> 21; s8 += carry7; s7 -= carry7 << 21; + carry9 = (s9 + (1<<20)) >> 21; s10 += carry9; s9 -= carry9 << 21; + carry11 = (s11 + (1<<20)) >> 21; s12 += carry11; s11 -= carry11 << 21; + + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + s12 = 0; + + carry0 = s0 >> 21; s1 += carry0; s0 -= carry0 << 21; + carry1 = s1 >> 21; s2 += carry1; s1 -= carry1 << 21; + carry2 = s2 >> 21; s3 += carry2; s2 -= carry2 << 21; + carry3 = s3 >> 21; s4 += carry3; s3 -= carry3 << 21; + carry4 = s4 >> 21; s5 += carry4; s4 -= carry4 << 21; + carry5 = s5 >> 21; s6 += carry5; s5 -= carry5 << 21; + carry6 = s6 >> 21; s7 += carry6; s6 -= carry6 << 21; + carry7 = s7 >> 21; s8 += carry7; s7 -= carry7 << 21; + carry8 = s8 >> 21; s9 += carry8; s8 -= carry8 << 21; + carry9 = s9 >> 21; s10 += carry9; s9 -= carry9 << 21; + carry10 = s10 >> 21; s11 += carry10; s10 -= carry10 << 21; + carry11 = s11 >> 21; s12 += carry11; s11 -= carry11 << 21; + + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + + carry0 = s0 >> 21; s1 += carry0; s0 -= carry0 << 21; + carry1 = s1 >> 21; s2 += carry1; s1 -= carry1 << 21; + carry2 = s2 >> 21; s3 += carry2; s2 -= carry2 << 21; + carry3 = s3 >> 21; s4 += carry3; s3 -= carry3 << 21; + carry4 = s4 >> 21; s5 += carry4; s4 -= carry4 << 21; + carry5 = s5 >> 21; s6 += carry5; s5 -= carry5 << 21; + carry6 = s6 >> 21; s7 += carry6; s6 -= carry6 << 21; + carry7 = s7 >> 21; s8 += carry7; s7 -= carry7 << 21; + carry8 = s8 >> 21; s9 += carry8; s8 -= carry8 << 21; + carry9 = s9 >> 21; s10 += carry9; s9 -= carry9 << 21; + carry10 = s10 >> 21; s11 += carry10; s10 -= carry10 << 21; + + s[0] = s0 >> 0; + s[1] = s0 >> 8; + s[2] = (s0 >> 16) | (s1 << 5); + s[3] = s1 >> 3; + s[4] = s1 >> 11; + s[5] = (s1 >> 19) | (s2 << 2); + s[6] = s2 >> 6; + s[7] = (s2 >> 14) | (s3 << 7); + s[8] = s3 >> 1; + s[9] = s3 >> 9; + s[10] = (s3 >> 17) | (s4 << 4); + s[11] = s4 >> 4; + s[12] = s4 >> 12; + s[13] = (s4 >> 20) | (s5 << 1); + s[14] = s5 >> 7; + s[15] = (s5 >> 15) | (s6 << 6); + s[16] = s6 >> 2; + s[17] = s6 >> 10; + s[18] = (s6 >> 18) | (s7 << 3); + s[19] = s7 >> 5; + s[20] = s7 >> 13; + s[21] = s8 >> 0; + s[22] = s8 >> 8; + s[23] = (s8 >> 16) | (s9 << 5); + s[24] = s9 >> 3; + s[25] = s9 >> 11; + s[26] = (s9 >> 19) | (s10 << 2); + s[27] = s10 >> 6; + s[28] = (s10 >> 14) | (s11 << 7); + s[29] = s11 >> 1; + s[30] = s11 >> 9; + s[31] = s11 >> 17; +} + +void sc_add(unsigned char *s, const unsigned char *a, const unsigned char *b) { + int64_t a0 = 2097151 & load_3(a); + int64_t a1 = 2097151 & (load_4(a + 2) >> 5); + int64_t a2 = 2097151 & (load_3(a + 5) >> 2); + int64_t a3 = 2097151 & (load_4(a + 7) >> 7); + int64_t a4 = 2097151 & (load_4(a + 10) >> 4); + int64_t a5 = 2097151 & (load_3(a + 13) >> 1); + int64_t a6 = 2097151 & (load_4(a + 15) >> 6); + int64_t a7 = 2097151 & (load_3(a + 18) >> 3); + int64_t a8 = 2097151 & load_3(a + 21); + int64_t a9 = 2097151 & (load_4(a + 23) >> 5); + int64_t a10 = 2097151 & (load_3(a + 26) >> 2); + int64_t a11 = (load_4(a + 28) >> 7); + int64_t b0 = 2097151 & load_3(b); + int64_t b1 = 2097151 & (load_4(b + 2) >> 5); + int64_t b2 = 2097151 & (load_3(b + 5) >> 2); + int64_t b3 = 2097151 & (load_4(b + 7) >> 7); + int64_t b4 = 2097151 & (load_4(b + 10) >> 4); + int64_t b5 = 2097151 & (load_3(b + 13) >> 1); + int64_t b6 = 2097151 & (load_4(b + 15) >> 6); + int64_t b7 = 2097151 & (load_3(b + 18) >> 3); + int64_t b8 = 2097151 & load_3(b + 21); + int64_t b9 = 2097151 & (load_4(b + 23) >> 5); + int64_t b10 = 2097151 & (load_3(b + 26) >> 2); + int64_t b11 = (load_4(b + 28) >> 7); + int64_t s0 = a0 + b0; + int64_t s1 = a1 + b1; + int64_t s2 = a2 + b2; + int64_t s3 = a3 + b3; + int64_t s4 = a4 + b4; + int64_t s5 = a5 + b5; + int64_t s6 = a6 + b6; + int64_t s7 = a7 + b7; + int64_t s8 = a8 + b8; + int64_t s9 = a9 + b9; + int64_t s10 = a10 + b10; + int64_t s11 = a11 + b11; + int64_t s12 = 0; + int64_t carry0; + int64_t carry1; + int64_t carry2; + int64_t carry3; + int64_t carry4; + int64_t carry5; + int64_t carry6; + int64_t carry7; + int64_t carry8; + int64_t carry9; + int64_t carry10; + int64_t carry11; + + carry0 = (s0 + (1<<20)) >> 21; s1 += carry0; s0 -= carry0 << 21; + carry2 = (s2 + (1<<20)) >> 21; s3 += carry2; s2 -= carry2 << 21; + carry4 = (s4 + (1<<20)) >> 21; s5 += carry4; s4 -= carry4 << 21; + carry6 = (s6 + (1<<20)) >> 21; s7 += carry6; s6 -= carry6 << 21; + carry8 = (s8 + (1<<20)) >> 21; s9 += carry8; s8 -= carry8 << 21; + carry10 = (s10 + (1<<20)) >> 21; s11 += carry10; s10 -= carry10 << 21; + + carry1 = (s1 + (1<<20)) >> 21; s2 += carry1; s1 -= carry1 << 21; + carry3 = (s3 + (1<<20)) >> 21; s4 += carry3; s3 -= carry3 << 21; + carry5 = (s5 + (1<<20)) >> 21; s6 += carry5; s5 -= carry5 << 21; + carry7 = (s7 + (1<<20)) >> 21; s8 += carry7; s7 -= carry7 << 21; + carry9 = (s9 + (1<<20)) >> 21; s10 += carry9; s9 -= carry9 << 21; + carry11 = (s11 + (1<<20)) >> 21; s12 += carry11; s11 -= carry11 << 21; + + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + s12 = 0; + + carry0 = s0 >> 21; s1 += carry0; s0 -= carry0 << 21; + carry1 = s1 >> 21; s2 += carry1; s1 -= carry1 << 21; + carry2 = s2 >> 21; s3 += carry2; s2 -= carry2 << 21; + carry3 = s3 >> 21; s4 += carry3; s3 -= carry3 << 21; + carry4 = s4 >> 21; s5 += carry4; s4 -= carry4 << 21; + carry5 = s5 >> 21; s6 += carry5; s5 -= carry5 << 21; + carry6 = s6 >> 21; s7 += carry6; s6 -= carry6 << 21; + carry7 = s7 >> 21; s8 += carry7; s7 -= carry7 << 21; + carry8 = s8 >> 21; s9 += carry8; s8 -= carry8 << 21; + carry9 = s9 >> 21; s10 += carry9; s9 -= carry9 << 21; + carry10 = s10 >> 21; s11 += carry10; s10 -= carry10 << 21; + carry11 = s11 >> 21; s12 += carry11; s11 -= carry11 << 21; + + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + + carry0 = s0 >> 21; s1 += carry0; s0 -= carry0 << 21; + carry1 = s1 >> 21; s2 += carry1; s1 -= carry1 << 21; + carry2 = s2 >> 21; s3 += carry2; s2 -= carry2 << 21; + carry3 = s3 >> 21; s4 += carry3; s3 -= carry3 << 21; + carry4 = s4 >> 21; s5 += carry4; s4 -= carry4 << 21; + carry5 = s5 >> 21; s6 += carry5; s5 -= carry5 << 21; + carry6 = s6 >> 21; s7 += carry6; s6 -= carry6 << 21; + carry7 = s7 >> 21; s8 += carry7; s7 -= carry7 << 21; + carry8 = s8 >> 21; s9 += carry8; s8 -= carry8 << 21; + carry9 = s9 >> 21; s10 += carry9; s9 -= carry9 << 21; + carry10 = s10 >> 21; s11 += carry10; s10 -= carry10 << 21; + + s[0] = s0 >> 0; + s[1] = s0 >> 8; + s[2] = (s0 >> 16) | (s1 << 5); + s[3] = s1 >> 3; + s[4] = s1 >> 11; + s[5] = (s1 >> 19) | (s2 << 2); + s[6] = s2 >> 6; + s[7] = (s2 >> 14) | (s3 << 7); + s[8] = s3 >> 1; + s[9] = s3 >> 9; + s[10] = (s3 >> 17) | (s4 << 4); + s[11] = s4 >> 4; + s[12] = s4 >> 12; + s[13] = (s4 >> 20) | (s5 << 1); + s[14] = s5 >> 7; + s[15] = (s5 >> 15) | (s6 << 6); + s[16] = s6 >> 2; + s[17] = s6 >> 10; + s[18] = (s6 >> 18) | (s7 << 3); + s[19] = s7 >> 5; + s[20] = s7 >> 13; + s[21] = s8 >> 0; + s[22] = s8 >> 8; + s[23] = (s8 >> 16) | (s9 << 5); + s[24] = s9 >> 3; + s[25] = s9 >> 11; + s[26] = (s9 >> 19) | (s10 << 2); + s[27] = s10 >> 6; + s[28] = (s10 >> 14) | (s11 << 7); + s[29] = s11 >> 1; + s[30] = s11 >> 9; + s[31] = s11 >> 17; +} + +void sc_sub(unsigned char *s, const unsigned char *a, const unsigned char *b) { + int64_t a0 = 2097151 & load_3(a); + int64_t a1 = 2097151 & (load_4(a + 2) >> 5); + int64_t a2 = 2097151 & (load_3(a + 5) >> 2); + int64_t a3 = 2097151 & (load_4(a + 7) >> 7); + int64_t a4 = 2097151 & (load_4(a + 10) >> 4); + int64_t a5 = 2097151 & (load_3(a + 13) >> 1); + int64_t a6 = 2097151 & (load_4(a + 15) >> 6); + int64_t a7 = 2097151 & (load_3(a + 18) >> 3); + int64_t a8 = 2097151 & load_3(a + 21); + int64_t a9 = 2097151 & (load_4(a + 23) >> 5); + int64_t a10 = 2097151 & (load_3(a + 26) >> 2); + int64_t a11 = (load_4(a + 28) >> 7); + int64_t b0 = 2097151 & load_3(b); + int64_t b1 = 2097151 & (load_4(b + 2) >> 5); + int64_t b2 = 2097151 & (load_3(b + 5) >> 2); + int64_t b3 = 2097151 & (load_4(b + 7) >> 7); + int64_t b4 = 2097151 & (load_4(b + 10) >> 4); + int64_t b5 = 2097151 & (load_3(b + 13) >> 1); + int64_t b6 = 2097151 & (load_4(b + 15) >> 6); + int64_t b7 = 2097151 & (load_3(b + 18) >> 3); + int64_t b8 = 2097151 & load_3(b + 21); + int64_t b9 = 2097151 & (load_4(b + 23) >> 5); + int64_t b10 = 2097151 & (load_3(b + 26) >> 2); + int64_t b11 = (load_4(b + 28) >> 7); + int64_t s0 = a0 - b0; + int64_t s1 = a1 - b1; + int64_t s2 = a2 - b2; + int64_t s3 = a3 - b3; + int64_t s4 = a4 - b4; + int64_t s5 = a5 - b5; + int64_t s6 = a6 - b6; + int64_t s7 = a7 - b7; + int64_t s8 = a8 - b8; + int64_t s9 = a9 - b9; + int64_t s10 = a10 - b10; + int64_t s11 = a11 - b11; + int64_t s12 = 0; + int64_t carry0; + int64_t carry1; + int64_t carry2; + int64_t carry3; + int64_t carry4; + int64_t carry5; + int64_t carry6; + int64_t carry7; + int64_t carry8; + int64_t carry9; + int64_t carry10; + int64_t carry11; + + carry0 = (s0 + (1<<20)) >> 21; s1 += carry0; s0 -= carry0 << 21; + carry2 = (s2 + (1<<20)) >> 21; s3 += carry2; s2 -= carry2 << 21; + carry4 = (s4 + (1<<20)) >> 21; s5 += carry4; s4 -= carry4 << 21; + carry6 = (s6 + (1<<20)) >> 21; s7 += carry6; s6 -= carry6 << 21; + carry8 = (s8 + (1<<20)) >> 21; s9 += carry8; s8 -= carry8 << 21; + carry10 = (s10 + (1<<20)) >> 21; s11 += carry10; s10 -= carry10 << 21; + + carry1 = (s1 + (1<<20)) >> 21; s2 += carry1; s1 -= carry1 << 21; + carry3 = (s3 + (1<<20)) >> 21; s4 += carry3; s3 -= carry3 << 21; + carry5 = (s5 + (1<<20)) >> 21; s6 += carry5; s5 -= carry5 << 21; + carry7 = (s7 + (1<<20)) >> 21; s8 += carry7; s7 -= carry7 << 21; + carry9 = (s9 + (1<<20)) >> 21; s10 += carry9; s9 -= carry9 << 21; + carry11 = (s11 + (1<<20)) >> 21; s12 += carry11; s11 -= carry11 << 21; + + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + s12 = 0; + + carry0 = s0 >> 21; s1 += carry0; s0 -= carry0 << 21; + carry1 = s1 >> 21; s2 += carry1; s1 -= carry1 << 21; + carry2 = s2 >> 21; s3 += carry2; s2 -= carry2 << 21; + carry3 = s3 >> 21; s4 += carry3; s3 -= carry3 << 21; + carry4 = s4 >> 21; s5 += carry4; s4 -= carry4 << 21; + carry5 = s5 >> 21; s6 += carry5; s5 -= carry5 << 21; + carry6 = s6 >> 21; s7 += carry6; s6 -= carry6 << 21; + carry7 = s7 >> 21; s8 += carry7; s7 -= carry7 << 21; + carry8 = s8 >> 21; s9 += carry8; s8 -= carry8 << 21; + carry9 = s9 >> 21; s10 += carry9; s9 -= carry9 << 21; + carry10 = s10 >> 21; s11 += carry10; s10 -= carry10 << 21; + carry11 = s11 >> 21; s12 += carry11; s11 -= carry11 << 21; + + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + + carry0 = s0 >> 21; s1 += carry0; s0 -= carry0 << 21; + carry1 = s1 >> 21; s2 += carry1; s1 -= carry1 << 21; + carry2 = s2 >> 21; s3 += carry2; s2 -= carry2 << 21; + carry3 = s3 >> 21; s4 += carry3; s3 -= carry3 << 21; + carry4 = s4 >> 21; s5 += carry4; s4 -= carry4 << 21; + carry5 = s5 >> 21; s6 += carry5; s5 -= carry5 << 21; + carry6 = s6 >> 21; s7 += carry6; s6 -= carry6 << 21; + carry7 = s7 >> 21; s8 += carry7; s7 -= carry7 << 21; + carry8 = s8 >> 21; s9 += carry8; s8 -= carry8 << 21; + carry9 = s9 >> 21; s10 += carry9; s9 -= carry9 << 21; + carry10 = s10 >> 21; s11 += carry10; s10 -= carry10 << 21; + + s[0] = s0 >> 0; + s[1] = s0 >> 8; + s[2] = (s0 >> 16) | (s1 << 5); + s[3] = s1 >> 3; + s[4] = s1 >> 11; + s[5] = (s1 >> 19) | (s2 << 2); + s[6] = s2 >> 6; + s[7] = (s2 >> 14) | (s3 << 7); + s[8] = s3 >> 1; + s[9] = s3 >> 9; + s[10] = (s3 >> 17) | (s4 << 4); + s[11] = s4 >> 4; + s[12] = s4 >> 12; + s[13] = (s4 >> 20) | (s5 << 1); + s[14] = s5 >> 7; + s[15] = (s5 >> 15) | (s6 << 6); + s[16] = s6 >> 2; + s[17] = s6 >> 10; + s[18] = (s6 >> 18) | (s7 << 3); + s[19] = s7 >> 5; + s[20] = s7 >> 13; + s[21] = s8 >> 0; + s[22] = s8 >> 8; + s[23] = (s8 >> 16) | (s9 << 5); + s[24] = s9 >> 3; + s[25] = s9 >> 11; + s[26] = (s9 >> 19) | (s10 << 2); + s[27] = s10 >> 6; + s[28] = (s10 >> 14) | (s11 << 7); + s[29] = s11 >> 1; + s[30] = s11 >> 9; + s[31] = s11 >> 17; +} + +/* +Input: + a[0]+256*a[1]+...+256^31*a[31] = a + b[0]+256*b[1]+...+256^31*b[31] = b + c[0]+256*c[1]+...+256^31*c[31] = c + +Output: + s[0]+256*s[1]+...+256^31*s[31] = (c-ab) mod l + where l = 2^252 + 27742317777372353535851937790883648493. +*/ + +void sc_mulsub(unsigned char *s, const unsigned char *a, const unsigned char *b, const unsigned char *c) { + int64_t a0 = 2097151 & load_3(a); + int64_t a1 = 2097151 & (load_4(a + 2) >> 5); + int64_t a2 = 2097151 & (load_3(a + 5) >> 2); + int64_t a3 = 2097151 & (load_4(a + 7) >> 7); + int64_t a4 = 2097151 & (load_4(a + 10) >> 4); + int64_t a5 = 2097151 & (load_3(a + 13) >> 1); + int64_t a6 = 2097151 & (load_4(a + 15) >> 6); + int64_t a7 = 2097151 & (load_3(a + 18) >> 3); + int64_t a8 = 2097151 & load_3(a + 21); + int64_t a9 = 2097151 & (load_4(a + 23) >> 5); + int64_t a10 = 2097151 & (load_3(a + 26) >> 2); + int64_t a11 = (load_4(a + 28) >> 7); + int64_t b0 = 2097151 & load_3(b); + int64_t b1 = 2097151 & (load_4(b + 2) >> 5); + int64_t b2 = 2097151 & (load_3(b + 5) >> 2); + int64_t b3 = 2097151 & (load_4(b + 7) >> 7); + int64_t b4 = 2097151 & (load_4(b + 10) >> 4); + int64_t b5 = 2097151 & (load_3(b + 13) >> 1); + int64_t b6 = 2097151 & (load_4(b + 15) >> 6); + int64_t b7 = 2097151 & (load_3(b + 18) >> 3); + int64_t b8 = 2097151 & load_3(b + 21); + int64_t b9 = 2097151 & (load_4(b + 23) >> 5); + int64_t b10 = 2097151 & (load_3(b + 26) >> 2); + int64_t b11 = (load_4(b + 28) >> 7); + int64_t c0 = 2097151 & load_3(c); + int64_t c1 = 2097151 & (load_4(c + 2) >> 5); + int64_t c2 = 2097151 & (load_3(c + 5) >> 2); + int64_t c3 = 2097151 & (load_4(c + 7) >> 7); + int64_t c4 = 2097151 & (load_4(c + 10) >> 4); + int64_t c5 = 2097151 & (load_3(c + 13) >> 1); + int64_t c6 = 2097151 & (load_4(c + 15) >> 6); + int64_t c7 = 2097151 & (load_3(c + 18) >> 3); + int64_t c8 = 2097151 & load_3(c + 21); + int64_t c9 = 2097151 & (load_4(c + 23) >> 5); + int64_t c10 = 2097151 & (load_3(c + 26) >> 2); + int64_t c11 = (load_4(c + 28) >> 7); + int64_t s0; + int64_t s1; + int64_t s2; + int64_t s3; + int64_t s4; + int64_t s5; + int64_t s6; + int64_t s7; + int64_t s8; + int64_t s9; + int64_t s10; + int64_t s11; + int64_t s12; + int64_t s13; + int64_t s14; + int64_t s15; + int64_t s16; + int64_t s17; + int64_t s18; + int64_t s19; + int64_t s20; + int64_t s21; + int64_t s22; + int64_t s23; + int64_t carry0; + int64_t carry1; + int64_t carry2; + int64_t carry3; + int64_t carry4; + int64_t carry5; + int64_t carry6; + int64_t carry7; + int64_t carry8; + int64_t carry9; + int64_t carry10; + int64_t carry11; + int64_t carry12; + int64_t carry13; + int64_t carry14; + int64_t carry15; + int64_t carry16; + int64_t carry17; + int64_t carry18; + int64_t carry19; + int64_t carry20; + int64_t carry21; + int64_t carry22; + + s0 = c0 - a0*b0; + s1 = c1 - (a0*b1 + a1*b0); + s2 = c2 - (a0*b2 + a1*b1 + a2*b0); + s3 = c3 - (a0*b3 + a1*b2 + a2*b1 + a3*b0); + s4 = c4 - (a0*b4 + a1*b3 + a2*b2 + a3*b1 + a4*b0); + s5 = c5 - (a0*b5 + a1*b4 + a2*b3 + a3*b2 + a4*b1 + a5*b0); + s6 = c6 - (a0*b6 + a1*b5 + a2*b4 + a3*b3 + a4*b2 + a5*b1 + a6*b0); + s7 = c7 - (a0*b7 + a1*b6 + a2*b5 + a3*b4 + a4*b3 + a5*b2 + a6*b1 + a7*b0); + s8 = c8 - (a0*b8 + a1*b7 + a2*b6 + a3*b5 + a4*b4 + a5*b3 + a6*b2 + a7*b1 + a8*b0); + s9 = c9 - (a0*b9 + a1*b8 + a2*b7 + a3*b6 + a4*b5 + a5*b4 + a6*b3 + a7*b2 + a8*b1 + a9*b0); + s10 = c10 - (a0*b10 + a1*b9 + a2*b8 + a3*b7 + a4*b6 + a5*b5 + a6*b4 + a7*b3 + a8*b2 + a9*b1 + a10*b0); + s11 = c11 - (a0*b11 + a1*b10 + a2*b9 + a3*b8 + a4*b7 + a5*b6 + a6*b5 + a7*b4 + a8*b3 + a9*b2 + a10*b1 + a11*b0); + s12 = -(a1*b11 + a2*b10 + a3*b9 + a4*b8 + a5*b7 + a6*b6 + a7*b5 + a8*b4 + a9*b3 + a10*b2 + a11*b1); + s13 = -(a2*b11 + a3*b10 + a4*b9 + a5*b8 + a6*b7 + a7*b6 + a8*b5 + a9*b4 + a10*b3 + a11*b2); + s14 = -(a3*b11 + a4*b10 + a5*b9 + a6*b8 + a7*b7 + a8*b6 + a9*b5 + a10*b4 + a11*b3); + s15 = -(a4*b11 + a5*b10 + a6*b9 + a7*b8 + a8*b7 + a9*b6 + a10*b5 + a11*b4); + s16 = -(a5*b11 + a6*b10 + a7*b9 + a8*b8 + a9*b7 + a10*b6 + a11*b5); + s17 = -(a6*b11 + a7*b10 + a8*b9 + a9*b8 + a10*b7 + a11*b6); + s18 = -(a7*b11 + a8*b10 + a9*b9 + a10*b8 + a11*b7); + s19 = -(a8*b11 + a9*b10 + a10*b9 + a11*b8); + s20 = -(a9*b11 + a10*b10 + a11*b9); + s21 = -(a10*b11 + a11*b10); + s22 = -a11*b11; + s23 = 0; + + carry0 = (s0 + (1<<20)) >> 21; s1 += carry0; s0 -= carry0 << 21; + carry2 = (s2 + (1<<20)) >> 21; s3 += carry2; s2 -= carry2 << 21; + carry4 = (s4 + (1<<20)) >> 21; s5 += carry4; s4 -= carry4 << 21; + carry6 = (s6 + (1<<20)) >> 21; s7 += carry6; s6 -= carry6 << 21; + carry8 = (s8 + (1<<20)) >> 21; s9 += carry8; s8 -= carry8 << 21; + carry10 = (s10 + (1<<20)) >> 21; s11 += carry10; s10 -= carry10 << 21; + carry12 = (s12 + (1<<20)) >> 21; s13 += carry12; s12 -= carry12 << 21; + carry14 = (s14 + (1<<20)) >> 21; s15 += carry14; s14 -= carry14 << 21; + carry16 = (s16 + (1<<20)) >> 21; s17 += carry16; s16 -= carry16 << 21; + carry18 = (s18 + (1<<20)) >> 21; s19 += carry18; s18 -= carry18 << 21; + carry20 = (s20 + (1<<20)) >> 21; s21 += carry20; s20 -= carry20 << 21; + carry22 = (s22 + (1<<20)) >> 21; s23 += carry22; s22 -= carry22 << 21; + + carry1 = (s1 + (1<<20)) >> 21; s2 += carry1; s1 -= carry1 << 21; + carry3 = (s3 + (1<<20)) >> 21; s4 += carry3; s3 -= carry3 << 21; + carry5 = (s5 + (1<<20)) >> 21; s6 += carry5; s5 -= carry5 << 21; + carry7 = (s7 + (1<<20)) >> 21; s8 += carry7; s7 -= carry7 << 21; + carry9 = (s9 + (1<<20)) >> 21; s10 += carry9; s9 -= carry9 << 21; + carry11 = (s11 + (1<<20)) >> 21; s12 += carry11; s11 -= carry11 << 21; + carry13 = (s13 + (1<<20)) >> 21; s14 += carry13; s13 -= carry13 << 21; + carry15 = (s15 + (1<<20)) >> 21; s16 += carry15; s15 -= carry15 << 21; + carry17 = (s17 + (1<<20)) >> 21; s18 += carry17; s17 -= carry17 << 21; + carry19 = (s19 + (1<<20)) >> 21; s20 += carry19; s19 -= carry19 << 21; + carry21 = (s21 + (1<<20)) >> 21; s22 += carry21; s21 -= carry21 << 21; + + s11 += s23 * 666643; + s12 += s23 * 470296; + s13 += s23 * 654183; + s14 -= s23 * 997805; + s15 += s23 * 136657; + s16 -= s23 * 683901; + + s10 += s22 * 666643; + s11 += s22 * 470296; + s12 += s22 * 654183; + s13 -= s22 * 997805; + s14 += s22 * 136657; + s15 -= s22 * 683901; + + s9 += s21 * 666643; + s10 += s21 * 470296; + s11 += s21 * 654183; + s12 -= s21 * 997805; + s13 += s21 * 136657; + s14 -= s21 * 683901; + + s8 += s20 * 666643; + s9 += s20 * 470296; + s10 += s20 * 654183; + s11 -= s20 * 997805; + s12 += s20 * 136657; + s13 -= s20 * 683901; + + s7 += s19 * 666643; + s8 += s19 * 470296; + s9 += s19 * 654183; + s10 -= s19 * 997805; + s11 += s19 * 136657; + s12 -= s19 * 683901; + + s6 += s18 * 666643; + s7 += s18 * 470296; + s8 += s18 * 654183; + s9 -= s18 * 997805; + s10 += s18 * 136657; + s11 -= s18 * 683901; + + carry6 = (s6 + (1<<20)) >> 21; s7 += carry6; s6 -= carry6 << 21; + carry8 = (s8 + (1<<20)) >> 21; s9 += carry8; s8 -= carry8 << 21; + carry10 = (s10 + (1<<20)) >> 21; s11 += carry10; s10 -= carry10 << 21; + carry12 = (s12 + (1<<20)) >> 21; s13 += carry12; s12 -= carry12 << 21; + carry14 = (s14 + (1<<20)) >> 21; s15 += carry14; s14 -= carry14 << 21; + carry16 = (s16 + (1<<20)) >> 21; s17 += carry16; s16 -= carry16 << 21; + + carry7 = (s7 + (1<<20)) >> 21; s8 += carry7; s7 -= carry7 << 21; + carry9 = (s9 + (1<<20)) >> 21; s10 += carry9; s9 -= carry9 << 21; + carry11 = (s11 + (1<<20)) >> 21; s12 += carry11; s11 -= carry11 << 21; + carry13 = (s13 + (1<<20)) >> 21; s14 += carry13; s13 -= carry13 << 21; + carry15 = (s15 + (1<<20)) >> 21; s16 += carry15; s15 -= carry15 << 21; + + s5 += s17 * 666643; + s6 += s17 * 470296; + s7 += s17 * 654183; + s8 -= s17 * 997805; + s9 += s17 * 136657; + s10 -= s17 * 683901; + + s4 += s16 * 666643; + s5 += s16 * 470296; + s6 += s16 * 654183; + s7 -= s16 * 997805; + s8 += s16 * 136657; + s9 -= s16 * 683901; + + s3 += s15 * 666643; + s4 += s15 * 470296; + s5 += s15 * 654183; + s6 -= s15 * 997805; + s7 += s15 * 136657; + s8 -= s15 * 683901; + + s2 += s14 * 666643; + s3 += s14 * 470296; + s4 += s14 * 654183; + s5 -= s14 * 997805; + s6 += s14 * 136657; + s7 -= s14 * 683901; + + s1 += s13 * 666643; + s2 += s13 * 470296; + s3 += s13 * 654183; + s4 -= s13 * 997805; + s5 += s13 * 136657; + s6 -= s13 * 683901; + + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + s12 = 0; + + carry0 = (s0 + (1<<20)) >> 21; s1 += carry0; s0 -= carry0 << 21; + carry2 = (s2 + (1<<20)) >> 21; s3 += carry2; s2 -= carry2 << 21; + carry4 = (s4 + (1<<20)) >> 21; s5 += carry4; s4 -= carry4 << 21; + carry6 = (s6 + (1<<20)) >> 21; s7 += carry6; s6 -= carry6 << 21; + carry8 = (s8 + (1<<20)) >> 21; s9 += carry8; s8 -= carry8 << 21; + carry10 = (s10 + (1<<20)) >> 21; s11 += carry10; s10 -= carry10 << 21; + + carry1 = (s1 + (1<<20)) >> 21; s2 += carry1; s1 -= carry1 << 21; + carry3 = (s3 + (1<<20)) >> 21; s4 += carry3; s3 -= carry3 << 21; + carry5 = (s5 + (1<<20)) >> 21; s6 += carry5; s5 -= carry5 << 21; + carry7 = (s7 + (1<<20)) >> 21; s8 += carry7; s7 -= carry7 << 21; + carry9 = (s9 + (1<<20)) >> 21; s10 += carry9; s9 -= carry9 << 21; + carry11 = (s11 + (1<<20)) >> 21; s12 += carry11; s11 -= carry11 << 21; + + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + s12 = 0; + + carry0 = s0 >> 21; s1 += carry0; s0 -= carry0 << 21; + carry1 = s1 >> 21; s2 += carry1; s1 -= carry1 << 21; + carry2 = s2 >> 21; s3 += carry2; s2 -= carry2 << 21; + carry3 = s3 >> 21; s4 += carry3; s3 -= carry3 << 21; + carry4 = s4 >> 21; s5 += carry4; s4 -= carry4 << 21; + carry5 = s5 >> 21; s6 += carry5; s5 -= carry5 << 21; + carry6 = s6 >> 21; s7 += carry6; s6 -= carry6 << 21; + carry7 = s7 >> 21; s8 += carry7; s7 -= carry7 << 21; + carry8 = s8 >> 21; s9 += carry8; s8 -= carry8 << 21; + carry9 = s9 >> 21; s10 += carry9; s9 -= carry9 << 21; + carry10 = s10 >> 21; s11 += carry10; s10 -= carry10 << 21; + carry11 = s11 >> 21; s12 += carry11; s11 -= carry11 << 21; + + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + + carry0 = s0 >> 21; s1 += carry0; s0 -= carry0 << 21; + carry1 = s1 >> 21; s2 += carry1; s1 -= carry1 << 21; + carry2 = s2 >> 21; s3 += carry2; s2 -= carry2 << 21; + carry3 = s3 >> 21; s4 += carry3; s3 -= carry3 << 21; + carry4 = s4 >> 21; s5 += carry4; s4 -= carry4 << 21; + carry5 = s5 >> 21; s6 += carry5; s5 -= carry5 << 21; + carry6 = s6 >> 21; s7 += carry6; s6 -= carry6 << 21; + carry7 = s7 >> 21; s8 += carry7; s7 -= carry7 << 21; + carry8 = s8 >> 21; s9 += carry8; s8 -= carry8 << 21; + carry9 = s9 >> 21; s10 += carry9; s9 -= carry9 << 21; + carry10 = s10 >> 21; s11 += carry10; s10 -= carry10 << 21; + + s[0] = s0 >> 0; + s[1] = s0 >> 8; + s[2] = (s0 >> 16) | (s1 << 5); + s[3] = s1 >> 3; + s[4] = s1 >> 11; + s[5] = (s1 >> 19) | (s2 << 2); + s[6] = s2 >> 6; + s[7] = (s2 >> 14) | (s3 << 7); + s[8] = s3 >> 1; + s[9] = s3 >> 9; + s[10] = (s3 >> 17) | (s4 << 4); + s[11] = s4 >> 4; + s[12] = s4 >> 12; + s[13] = (s4 >> 20) | (s5 << 1); + s[14] = s5 >> 7; + s[15] = (s5 >> 15) | (s6 << 6); + s[16] = s6 >> 2; + s[17] = s6 >> 10; + s[18] = (s6 >> 18) | (s7 << 3); + s[19] = s7 >> 5; + s[20] = s7 >> 13; + s[21] = s8 >> 0; + s[22] = s8 >> 8; + s[23] = (s8 >> 16) | (s9 << 5); + s[24] = s9 >> 3; + s[25] = s9 >> 11; + s[26] = (s9 >> 19) | (s10 << 2); + s[27] = s10 >> 6; + s[28] = (s10 >> 14) | (s11 << 7); + s[29] = s11 >> 1; + s[30] = s11 >> 9; + s[31] = s11 >> 17; +} + +//copied from above and modified +/* +Input: + a[0]+256*a[1]+...+256^31*a[31] = a + b[0]+256*b[1]+...+256^31*b[31] = b + +Output: + s[0]+256*s[1]+...+256^31*s[31] = (ab) mod l + where l = 2^252 + 27742317777372353535851937790883648493. +*/ +void sc_mul(unsigned char *s, const unsigned char *a, const unsigned char *b) { + int64_t a0 = 2097151 & load_3(a); + int64_t a1 = 2097151 & (load_4(a + 2) >> 5); + int64_t a2 = 2097151 & (load_3(a + 5) >> 2); + int64_t a3 = 2097151 & (load_4(a + 7) >> 7); + int64_t a4 = 2097151 & (load_4(a + 10) >> 4); + int64_t a5 = 2097151 & (load_3(a + 13) >> 1); + int64_t a6 = 2097151 & (load_4(a + 15) >> 6); + int64_t a7 = 2097151 & (load_3(a + 18) >> 3); + int64_t a8 = 2097151 & load_3(a + 21); + int64_t a9 = 2097151 & (load_4(a + 23) >> 5); + int64_t a10 = 2097151 & (load_3(a + 26) >> 2); + int64_t a11 = (load_4(a + 28) >> 7); + int64_t b0 = 2097151 & load_3(b); + int64_t b1 = 2097151 & (load_4(b + 2) >> 5); + int64_t b2 = 2097151 & (load_3(b + 5) >> 2); + int64_t b3 = 2097151 & (load_4(b + 7) >> 7); + int64_t b4 = 2097151 & (load_4(b + 10) >> 4); + int64_t b5 = 2097151 & (load_3(b + 13) >> 1); + int64_t b6 = 2097151 & (load_4(b + 15) >> 6); + int64_t b7 = 2097151 & (load_3(b + 18) >> 3); + int64_t b8 = 2097151 & load_3(b + 21); + int64_t b9 = 2097151 & (load_4(b + 23) >> 5); + int64_t b10 = 2097151 & (load_3(b + 26) >> 2); + int64_t b11 = (load_4(b + 28) >> 7); + int64_t s0; + int64_t s1; + int64_t s2; + int64_t s3; + int64_t s4; + int64_t s5; + int64_t s6; + int64_t s7; + int64_t s8; + int64_t s9; + int64_t s10; + int64_t s11; + int64_t s12; + int64_t s13; + int64_t s14; + int64_t s15; + int64_t s16; + int64_t s17; + int64_t s18; + int64_t s19; + int64_t s20; + int64_t s21; + int64_t s22; + int64_t s23; + int64_t carry0; + int64_t carry1; + int64_t carry2; + int64_t carry3; + int64_t carry4; + int64_t carry5; + int64_t carry6; + int64_t carry7; + int64_t carry8; + int64_t carry9; + int64_t carry10; + int64_t carry11; + int64_t carry12; + int64_t carry13; + int64_t carry14; + int64_t carry15; + int64_t carry16; + int64_t carry17; + int64_t carry18; + int64_t carry19; + int64_t carry20; + int64_t carry21; + int64_t carry22; + + s0 = a0*b0; + s1 = (a0*b1 + a1*b0); + s2 = (a0*b2 + a1*b1 + a2*b0); + s3 = (a0*b3 + a1*b2 + a2*b1 + a3*b0); + s4 = (a0*b4 + a1*b3 + a2*b2 + a3*b1 + a4*b0); + s5 = (a0*b5 + a1*b4 + a2*b3 + a3*b2 + a4*b1 + a5*b0); + s6 = (a0*b6 + a1*b5 + a2*b4 + a3*b3 + a4*b2 + a5*b1 + a6*b0); + s7 = (a0*b7 + a1*b6 + a2*b5 + a3*b4 + a4*b3 + a5*b2 + a6*b1 + a7*b0); + s8 = (a0*b8 + a1*b7 + a2*b6 + a3*b5 + a4*b4 + a5*b3 + a6*b2 + a7*b1 + a8*b0); + s9 = (a0*b9 + a1*b8 + a2*b7 + a3*b6 + a4*b5 + a5*b4 + a6*b3 + a7*b2 + a8*b1 + a9*b0); + s10 = (a0*b10 + a1*b9 + a2*b8 + a3*b7 + a4*b6 + a5*b5 + a6*b4 + a7*b3 + a8*b2 + a9*b1 + a10*b0); + s11 = (a0*b11 + a1*b10 + a2*b9 + a3*b8 + a4*b7 + a5*b6 + a6*b5 + a7*b4 + a8*b3 + a9*b2 + a10*b1 + a11*b0); + s12 = (a1*b11 + a2*b10 + a3*b9 + a4*b8 + a5*b7 + a6*b6 + a7*b5 + a8*b4 + a9*b3 + a10*b2 + a11*b1); + s13 = (a2*b11 + a3*b10 + a4*b9 + a5*b8 + a6*b7 + a7*b6 + a8*b5 + a9*b4 + a10*b3 + a11*b2); + s14 = (a3*b11 + a4*b10 + a5*b9 + a6*b8 + a7*b7 + a8*b6 + a9*b5 + a10*b4 + a11*b3); + s15 = (a4*b11 + a5*b10 + a6*b9 + a7*b8 + a8*b7 + a9*b6 + a10*b5 + a11*b4); + s16 = (a5*b11 + a6*b10 + a7*b9 + a8*b8 + a9*b7 + a10*b6 + a11*b5); + s17 = (a6*b11 + a7*b10 + a8*b9 + a9*b8 + a10*b7 + a11*b6); + s18 = (a7*b11 + a8*b10 + a9*b9 + a10*b8 + a11*b7); + s19 = (a8*b11 + a9*b10 + a10*b9 + a11*b8); + s20 = (a9*b11 + a10*b10 + a11*b9); + s21 = (a10*b11 + a11*b10); + s22 = a11*b11; + s23 = 0; + + carry0 = (s0 + (1<<20)) >> 21; s1 += carry0; s0 -= carry0 << 21; + carry2 = (s2 + (1<<20)) >> 21; s3 += carry2; s2 -= carry2 << 21; + carry4 = (s4 + (1<<20)) >> 21; s5 += carry4; s4 -= carry4 << 21; + carry6 = (s6 + (1<<20)) >> 21; s7 += carry6; s6 -= carry6 << 21; + carry8 = (s8 + (1<<20)) >> 21; s9 += carry8; s8 -= carry8 << 21; + carry10 = (s10 + (1<<20)) >> 21; s11 += carry10; s10 -= carry10 << 21; + carry12 = (s12 + (1<<20)) >> 21; s13 += carry12; s12 -= carry12 << 21; + carry14 = (s14 + (1<<20)) >> 21; s15 += carry14; s14 -= carry14 << 21; + carry16 = (s16 + (1<<20)) >> 21; s17 += carry16; s16 -= carry16 << 21; + carry18 = (s18 + (1<<20)) >> 21; s19 += carry18; s18 -= carry18 << 21; + carry20 = (s20 + (1<<20)) >> 21; s21 += carry20; s20 -= carry20 << 21; + carry22 = (s22 + (1<<20)) >> 21; s23 += carry22; s22 -= carry22 << 21; + + carry1 = (s1 + (1<<20)) >> 21; s2 += carry1; s1 -= carry1 << 21; + carry3 = (s3 + (1<<20)) >> 21; s4 += carry3; s3 -= carry3 << 21; + carry5 = (s5 + (1<<20)) >> 21; s6 += carry5; s5 -= carry5 << 21; + carry7 = (s7 + (1<<20)) >> 21; s8 += carry7; s7 -= carry7 << 21; + carry9 = (s9 + (1<<20)) >> 21; s10 += carry9; s9 -= carry9 << 21; + carry11 = (s11 + (1<<20)) >> 21; s12 += carry11; s11 -= carry11 << 21; + carry13 = (s13 + (1<<20)) >> 21; s14 += carry13; s13 -= carry13 << 21; + carry15 = (s15 + (1<<20)) >> 21; s16 += carry15; s15 -= carry15 << 21; + carry17 = (s17 + (1<<20)) >> 21; s18 += carry17; s17 -= carry17 << 21; + carry19 = (s19 + (1<<20)) >> 21; s20 += carry19; s19 -= carry19 << 21; + carry21 = (s21 + (1<<20)) >> 21; s22 += carry21; s21 -= carry21 << 21; + + s11 += s23 * 666643; + s12 += s23 * 470296; + s13 += s23 * 654183; + s14 -= s23 * 997805; + s15 += s23 * 136657; + s16 -= s23 * 683901; + + s10 += s22 * 666643; + s11 += s22 * 470296; + s12 += s22 * 654183; + s13 -= s22 * 997805; + s14 += s22 * 136657; + s15 -= s22 * 683901; + + s9 += s21 * 666643; + s10 += s21 * 470296; + s11 += s21 * 654183; + s12 -= s21 * 997805; + s13 += s21 * 136657; + s14 -= s21 * 683901; + + s8 += s20 * 666643; + s9 += s20 * 470296; + s10 += s20 * 654183; + s11 -= s20 * 997805; + s12 += s20 * 136657; + s13 -= s20 * 683901; + + s7 += s19 * 666643; + s8 += s19 * 470296; + s9 += s19 * 654183; + s10 -= s19 * 997805; + s11 += s19 * 136657; + s12 -= s19 * 683901; + + s6 += s18 * 666643; + s7 += s18 * 470296; + s8 += s18 * 654183; + s9 -= s18 * 997805; + s10 += s18 * 136657; + s11 -= s18 * 683901; + + carry6 = (s6 + (1<<20)) >> 21; s7 += carry6; s6 -= carry6 << 21; + carry8 = (s8 + (1<<20)) >> 21; s9 += carry8; s8 -= carry8 << 21; + carry10 = (s10 + (1<<20)) >> 21; s11 += carry10; s10 -= carry10 << 21; + carry12 = (s12 + (1<<20)) >> 21; s13 += carry12; s12 -= carry12 << 21; + carry14 = (s14 + (1<<20)) >> 21; s15 += carry14; s14 -= carry14 << 21; + carry16 = (s16 + (1<<20)) >> 21; s17 += carry16; s16 -= carry16 << 21; + + carry7 = (s7 + (1<<20)) >> 21; s8 += carry7; s7 -= carry7 << 21; + carry9 = (s9 + (1<<20)) >> 21; s10 += carry9; s9 -= carry9 << 21; + carry11 = (s11 + (1<<20)) >> 21; s12 += carry11; s11 -= carry11 << 21; + carry13 = (s13 + (1<<20)) >> 21; s14 += carry13; s13 -= carry13 << 21; + carry15 = (s15 + (1<<20)) >> 21; s16 += carry15; s15 -= carry15 << 21; + + s5 += s17 * 666643; + s6 += s17 * 470296; + s7 += s17 * 654183; + s8 -= s17 * 997805; + s9 += s17 * 136657; + s10 -= s17 * 683901; + + s4 += s16 * 666643; + s5 += s16 * 470296; + s6 += s16 * 654183; + s7 -= s16 * 997805; + s8 += s16 * 136657; + s9 -= s16 * 683901; + + s3 += s15 * 666643; + s4 += s15 * 470296; + s5 += s15 * 654183; + s6 -= s15 * 997805; + s7 += s15 * 136657; + s8 -= s15 * 683901; + + s2 += s14 * 666643; + s3 += s14 * 470296; + s4 += s14 * 654183; + s5 -= s14 * 997805; + s6 += s14 * 136657; + s7 -= s14 * 683901; + + s1 += s13 * 666643; + s2 += s13 * 470296; + s3 += s13 * 654183; + s4 -= s13 * 997805; + s5 += s13 * 136657; + s6 -= s13 * 683901; + + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + s12 = 0; + + carry0 = (s0 + (1<<20)) >> 21; s1 += carry0; s0 -= carry0 << 21; + carry2 = (s2 + (1<<20)) >> 21; s3 += carry2; s2 -= carry2 << 21; + carry4 = (s4 + (1<<20)) >> 21; s5 += carry4; s4 -= carry4 << 21; + carry6 = (s6 + (1<<20)) >> 21; s7 += carry6; s6 -= carry6 << 21; + carry8 = (s8 + (1<<20)) >> 21; s9 += carry8; s8 -= carry8 << 21; + carry10 = (s10 + (1<<20)) >> 21; s11 += carry10; s10 -= carry10 << 21; + + carry1 = (s1 + (1<<20)) >> 21; s2 += carry1; s1 -= carry1 << 21; + carry3 = (s3 + (1<<20)) >> 21; s4 += carry3; s3 -= carry3 << 21; + carry5 = (s5 + (1<<20)) >> 21; s6 += carry5; s5 -= carry5 << 21; + carry7 = (s7 + (1<<20)) >> 21; s8 += carry7; s7 -= carry7 << 21; + carry9 = (s9 + (1<<20)) >> 21; s10 += carry9; s9 -= carry9 << 21; + carry11 = (s11 + (1<<20)) >> 21; s12 += carry11; s11 -= carry11 << 21; + + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + s12 = 0; + + carry0 = s0 >> 21; s1 += carry0; s0 -= carry0 << 21; + carry1 = s1 >> 21; s2 += carry1; s1 -= carry1 << 21; + carry2 = s2 >> 21; s3 += carry2; s2 -= carry2 << 21; + carry3 = s3 >> 21; s4 += carry3; s3 -= carry3 << 21; + carry4 = s4 >> 21; s5 += carry4; s4 -= carry4 << 21; + carry5 = s5 >> 21; s6 += carry5; s5 -= carry5 << 21; + carry6 = s6 >> 21; s7 += carry6; s6 -= carry6 << 21; + carry7 = s7 >> 21; s8 += carry7; s7 -= carry7 << 21; + carry8 = s8 >> 21; s9 += carry8; s8 -= carry8 << 21; + carry9 = s9 >> 21; s10 += carry9; s9 -= carry9 << 21; + carry10 = s10 >> 21; s11 += carry10; s10 -= carry10 << 21; + carry11 = s11 >> 21; s12 += carry11; s11 -= carry11 << 21; + + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + + carry0 = s0 >> 21; s1 += carry0; s0 -= carry0 << 21; + carry1 = s1 >> 21; s2 += carry1; s1 -= carry1 << 21; + carry2 = s2 >> 21; s3 += carry2; s2 -= carry2 << 21; + carry3 = s3 >> 21; s4 += carry3; s3 -= carry3 << 21; + carry4 = s4 >> 21; s5 += carry4; s4 -= carry4 << 21; + carry5 = s5 >> 21; s6 += carry5; s5 -= carry5 << 21; + carry6 = s6 >> 21; s7 += carry6; s6 -= carry6 << 21; + carry7 = s7 >> 21; s8 += carry7; s7 -= carry7 << 21; + carry8 = s8 >> 21; s9 += carry8; s8 -= carry8 << 21; + carry9 = s9 >> 21; s10 += carry9; s9 -= carry9 << 21; + carry10 = s10 >> 21; s11 += carry10; s10 -= carry10 << 21; + + s[0] = s0 >> 0; + s[1] = s0 >> 8; + s[2] = (s0 >> 16) | (s1 << 5); + s[3] = s1 >> 3; + s[4] = s1 >> 11; + s[5] = (s1 >> 19) | (s2 << 2); + s[6] = s2 >> 6; + s[7] = (s2 >> 14) | (s3 << 7); + s[8] = s3 >> 1; + s[9] = s3 >> 9; + s[10] = (s3 >> 17) | (s4 << 4); + s[11] = s4 >> 4; + s[12] = s4 >> 12; + s[13] = (s4 >> 20) | (s5 << 1); + s[14] = s5 >> 7; + s[15] = (s5 >> 15) | (s6 << 6); + s[16] = s6 >> 2; + s[17] = s6 >> 10; + s[18] = (s6 >> 18) | (s7 << 3); + s[19] = s7 >> 5; + s[20] = s7 >> 13; + s[21] = s8 >> 0; + s[22] = s8 >> 8; + s[23] = (s8 >> 16) | (s9 << 5); + s[24] = s9 >> 3; + s[25] = s9 >> 11; + s[26] = (s9 >> 19) | (s10 << 2); + s[27] = s10 >> 6; + s[28] = (s10 >> 14) | (s11 << 7); + s[29] = s11 >> 1; + s[30] = s11 >> 9; + s[31] = s11 >> 17; +} + +//copied from above and modified +/* +Input: + a[0]+256*a[1]+...+256^31*a[31] = a + b[0]+256*b[1]+...+256^31*b[31] = b + c[0]+256*c[1]+...+256^31*c[31] = c + +Output: + s[0]+256*s[1]+...+256^31*s[31] = (c+ab) mod l + where l = 2^252 + 27742317777372353535851937790883648493. +*/ + +void sc_muladd(unsigned char *s, const unsigned char *a, const unsigned char *b, const unsigned char *c) { + int64_t a0 = 2097151 & load_3(a); + int64_t a1 = 2097151 & (load_4(a + 2) >> 5); + int64_t a2 = 2097151 & (load_3(a + 5) >> 2); + int64_t a3 = 2097151 & (load_4(a + 7) >> 7); + int64_t a4 = 2097151 & (load_4(a + 10) >> 4); + int64_t a5 = 2097151 & (load_3(a + 13) >> 1); + int64_t a6 = 2097151 & (load_4(a + 15) >> 6); + int64_t a7 = 2097151 & (load_3(a + 18) >> 3); + int64_t a8 = 2097151 & load_3(a + 21); + int64_t a9 = 2097151 & (load_4(a + 23) >> 5); + int64_t a10 = 2097151 & (load_3(a + 26) >> 2); + int64_t a11 = (load_4(a + 28) >> 7); + int64_t b0 = 2097151 & load_3(b); + int64_t b1 = 2097151 & (load_4(b + 2) >> 5); + int64_t b2 = 2097151 & (load_3(b + 5) >> 2); + int64_t b3 = 2097151 & (load_4(b + 7) >> 7); + int64_t b4 = 2097151 & (load_4(b + 10) >> 4); + int64_t b5 = 2097151 & (load_3(b + 13) >> 1); + int64_t b6 = 2097151 & (load_4(b + 15) >> 6); + int64_t b7 = 2097151 & (load_3(b + 18) >> 3); + int64_t b8 = 2097151 & load_3(b + 21); + int64_t b9 = 2097151 & (load_4(b + 23) >> 5); + int64_t b10 = 2097151 & (load_3(b + 26) >> 2); + int64_t b11 = (load_4(b + 28) >> 7); + int64_t c0 = 2097151 & load_3(c); + int64_t c1 = 2097151 & (load_4(c + 2) >> 5); + int64_t c2 = 2097151 & (load_3(c + 5) >> 2); + int64_t c3 = 2097151 & (load_4(c + 7) >> 7); + int64_t c4 = 2097151 & (load_4(c + 10) >> 4); + int64_t c5 = 2097151 & (load_3(c + 13) >> 1); + int64_t c6 = 2097151 & (load_4(c + 15) >> 6); + int64_t c7 = 2097151 & (load_3(c + 18) >> 3); + int64_t c8 = 2097151 & load_3(c + 21); + int64_t c9 = 2097151 & (load_4(c + 23) >> 5); + int64_t c10 = 2097151 & (load_3(c + 26) >> 2); + int64_t c11 = (load_4(c + 28) >> 7); + int64_t s0; + int64_t s1; + int64_t s2; + int64_t s3; + int64_t s4; + int64_t s5; + int64_t s6; + int64_t s7; + int64_t s8; + int64_t s9; + int64_t s10; + int64_t s11; + int64_t s12; + int64_t s13; + int64_t s14; + int64_t s15; + int64_t s16; + int64_t s17; + int64_t s18; + int64_t s19; + int64_t s20; + int64_t s21; + int64_t s22; + int64_t s23; + int64_t carry0; + int64_t carry1; + int64_t carry2; + int64_t carry3; + int64_t carry4; + int64_t carry5; + int64_t carry6; + int64_t carry7; + int64_t carry8; + int64_t carry9; + int64_t carry10; + int64_t carry11; + int64_t carry12; + int64_t carry13; + int64_t carry14; + int64_t carry15; + int64_t carry16; + int64_t carry17; + int64_t carry18; + int64_t carry19; + int64_t carry20; + int64_t carry21; + int64_t carry22; + + s0 = c0 + a0*b0; + s1 = c1 + (a0*b1 + a1*b0); + s2 = c2 + (a0*b2 + a1*b1 + a2*b0); + s3 = c3 + (a0*b3 + a1*b2 + a2*b1 + a3*b0); + s4 = c4 + (a0*b4 + a1*b3 + a2*b2 + a3*b1 + a4*b0); + s5 = c5 + (a0*b5 + a1*b4 + a2*b3 + a3*b2 + a4*b1 + a5*b0); + s6 = c6 + (a0*b6 + a1*b5 + a2*b4 + a3*b3 + a4*b2 + a5*b1 + a6*b0); + s7 = c7 + (a0*b7 + a1*b6 + a2*b5 + a3*b4 + a4*b3 + a5*b2 + a6*b1 + a7*b0); + s8 = c8 + (a0*b8 + a1*b7 + a2*b6 + a3*b5 + a4*b4 + a5*b3 + a6*b2 + a7*b1 + a8*b0); + s9 = c9 + (a0*b9 + a1*b8 + a2*b7 + a3*b6 + a4*b5 + a5*b4 + a6*b3 + a7*b2 + a8*b1 + a9*b0); + s10 = c10 + (a0*b10 + a1*b9 + a2*b8 + a3*b7 + a4*b6 + a5*b5 + a6*b4 + a7*b3 + a8*b2 + a9*b1 + a10*b0); + s11 = c11 + (a0*b11 + a1*b10 + a2*b9 + a3*b8 + a4*b7 + a5*b6 + a6*b5 + a7*b4 + a8*b3 + a9*b2 + a10*b1 + a11*b0); + s12 = (a1*b11 + a2*b10 + a3*b9 + a4*b8 + a5*b7 + a6*b6 + a7*b5 + a8*b4 + a9*b3 + a10*b2 + a11*b1); + s13 = (a2*b11 + a3*b10 + a4*b9 + a5*b8 + a6*b7 + a7*b6 + a8*b5 + a9*b4 + a10*b3 + a11*b2); + s14 = (a3*b11 + a4*b10 + a5*b9 + a6*b8 + a7*b7 + a8*b6 + a9*b5 + a10*b4 + a11*b3); + s15 = (a4*b11 + a5*b10 + a6*b9 + a7*b8 + a8*b7 + a9*b6 + a10*b5 + a11*b4); + s16 = (a5*b11 + a6*b10 + a7*b9 + a8*b8 + a9*b7 + a10*b6 + a11*b5); + s17 = (a6*b11 + a7*b10 + a8*b9 + a9*b8 + a10*b7 + a11*b6); + s18 = (a7*b11 + a8*b10 + a9*b9 + a10*b8 + a11*b7); + s19 = (a8*b11 + a9*b10 + a10*b9 + a11*b8); + s20 = (a9*b11 + a10*b10 + a11*b9); + s21 = (a10*b11 + a11*b10); + s22 = a11*b11; + s23 = 0; + + carry0 = (s0 + (1<<20)) >> 21; s1 += carry0; s0 -= carry0 << 21; + carry2 = (s2 + (1<<20)) >> 21; s3 += carry2; s2 -= carry2 << 21; + carry4 = (s4 + (1<<20)) >> 21; s5 += carry4; s4 -= carry4 << 21; + carry6 = (s6 + (1<<20)) >> 21; s7 += carry6; s6 -= carry6 << 21; + carry8 = (s8 + (1<<20)) >> 21; s9 += carry8; s8 -= carry8 << 21; + carry10 = (s10 + (1<<20)) >> 21; s11 += carry10; s10 -= carry10 << 21; + carry12 = (s12 + (1<<20)) >> 21; s13 += carry12; s12 -= carry12 << 21; + carry14 = (s14 + (1<<20)) >> 21; s15 += carry14; s14 -= carry14 << 21; + carry16 = (s16 + (1<<20)) >> 21; s17 += carry16; s16 -= carry16 << 21; + carry18 = (s18 + (1<<20)) >> 21; s19 += carry18; s18 -= carry18 << 21; + carry20 = (s20 + (1<<20)) >> 21; s21 += carry20; s20 -= carry20 << 21; + carry22 = (s22 + (1<<20)) >> 21; s23 += carry22; s22 -= carry22 << 21; + + carry1 = (s1 + (1<<20)) >> 21; s2 += carry1; s1 -= carry1 << 21; + carry3 = (s3 + (1<<20)) >> 21; s4 += carry3; s3 -= carry3 << 21; + carry5 = (s5 + (1<<20)) >> 21; s6 += carry5; s5 -= carry5 << 21; + carry7 = (s7 + (1<<20)) >> 21; s8 += carry7; s7 -= carry7 << 21; + carry9 = (s9 + (1<<20)) >> 21; s10 += carry9; s9 -= carry9 << 21; + carry11 = (s11 + (1<<20)) >> 21; s12 += carry11; s11 -= carry11 << 21; + carry13 = (s13 + (1<<20)) >> 21; s14 += carry13; s13 -= carry13 << 21; + carry15 = (s15 + (1<<20)) >> 21; s16 += carry15; s15 -= carry15 << 21; + carry17 = (s17 + (1<<20)) >> 21; s18 += carry17; s17 -= carry17 << 21; + carry19 = (s19 + (1<<20)) >> 21; s20 += carry19; s19 -= carry19 << 21; + carry21 = (s21 + (1<<20)) >> 21; s22 += carry21; s21 -= carry21 << 21; + + s11 += s23 * 666643; + s12 += s23 * 470296; + s13 += s23 * 654183; + s14 -= s23 * 997805; + s15 += s23 * 136657; + s16 -= s23 * 683901; + + s10 += s22 * 666643; + s11 += s22 * 470296; + s12 += s22 * 654183; + s13 -= s22 * 997805; + s14 += s22 * 136657; + s15 -= s22 * 683901; + + s9 += s21 * 666643; + s10 += s21 * 470296; + s11 += s21 * 654183; + s12 -= s21 * 997805; + s13 += s21 * 136657; + s14 -= s21 * 683901; + + s8 += s20 * 666643; + s9 += s20 * 470296; + s10 += s20 * 654183; + s11 -= s20 * 997805; + s12 += s20 * 136657; + s13 -= s20 * 683901; + + s7 += s19 * 666643; + s8 += s19 * 470296; + s9 += s19 * 654183; + s10 -= s19 * 997805; + s11 += s19 * 136657; + s12 -= s19 * 683901; + + s6 += s18 * 666643; + s7 += s18 * 470296; + s8 += s18 * 654183; + s9 -= s18 * 997805; + s10 += s18 * 136657; + s11 -= s18 * 683901; + + carry6 = (s6 + (1<<20)) >> 21; s7 += carry6; s6 -= carry6 << 21; + carry8 = (s8 + (1<<20)) >> 21; s9 += carry8; s8 -= carry8 << 21; + carry10 = (s10 + (1<<20)) >> 21; s11 += carry10; s10 -= carry10 << 21; + carry12 = (s12 + (1<<20)) >> 21; s13 += carry12; s12 -= carry12 << 21; + carry14 = (s14 + (1<<20)) >> 21; s15 += carry14; s14 -= carry14 << 21; + carry16 = (s16 + (1<<20)) >> 21; s17 += carry16; s16 -= carry16 << 21; + + carry7 = (s7 + (1<<20)) >> 21; s8 += carry7; s7 -= carry7 << 21; + carry9 = (s9 + (1<<20)) >> 21; s10 += carry9; s9 -= carry9 << 21; + carry11 = (s11 + (1<<20)) >> 21; s12 += carry11; s11 -= carry11 << 21; + carry13 = (s13 + (1<<20)) >> 21; s14 += carry13; s13 -= carry13 << 21; + carry15 = (s15 + (1<<20)) >> 21; s16 += carry15; s15 -= carry15 << 21; + + s5 += s17 * 666643; + s6 += s17 * 470296; + s7 += s17 * 654183; + s8 -= s17 * 997805; + s9 += s17 * 136657; + s10 -= s17 * 683901; + + s4 += s16 * 666643; + s5 += s16 * 470296; + s6 += s16 * 654183; + s7 -= s16 * 997805; + s8 += s16 * 136657; + s9 -= s16 * 683901; + + s3 += s15 * 666643; + s4 += s15 * 470296; + s5 += s15 * 654183; + s6 -= s15 * 997805; + s7 += s15 * 136657; + s8 -= s15 * 683901; + + s2 += s14 * 666643; + s3 += s14 * 470296; + s4 += s14 * 654183; + s5 -= s14 * 997805; + s6 += s14 * 136657; + s7 -= s14 * 683901; + + s1 += s13 * 666643; + s2 += s13 * 470296; + s3 += s13 * 654183; + s4 -= s13 * 997805; + s5 += s13 * 136657; + s6 -= s13 * 683901; + + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + s12 = 0; + + carry0 = (s0 + (1<<20)) >> 21; s1 += carry0; s0 -= carry0 << 21; + carry2 = (s2 + (1<<20)) >> 21; s3 += carry2; s2 -= carry2 << 21; + carry4 = (s4 + (1<<20)) >> 21; s5 += carry4; s4 -= carry4 << 21; + carry6 = (s6 + (1<<20)) >> 21; s7 += carry6; s6 -= carry6 << 21; + carry8 = (s8 + (1<<20)) >> 21; s9 += carry8; s8 -= carry8 << 21; + carry10 = (s10 + (1<<20)) >> 21; s11 += carry10; s10 -= carry10 << 21; + + carry1 = (s1 + (1<<20)) >> 21; s2 += carry1; s1 -= carry1 << 21; + carry3 = (s3 + (1<<20)) >> 21; s4 += carry3; s3 -= carry3 << 21; + carry5 = (s5 + (1<<20)) >> 21; s6 += carry5; s5 -= carry5 << 21; + carry7 = (s7 + (1<<20)) >> 21; s8 += carry7; s7 -= carry7 << 21; + carry9 = (s9 + (1<<20)) >> 21; s10 += carry9; s9 -= carry9 << 21; + carry11 = (s11 + (1<<20)) >> 21; s12 += carry11; s11 -= carry11 << 21; + + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + s12 = 0; + + carry0 = s0 >> 21; s1 += carry0; s0 -= carry0 << 21; + carry1 = s1 >> 21; s2 += carry1; s1 -= carry1 << 21; + carry2 = s2 >> 21; s3 += carry2; s2 -= carry2 << 21; + carry3 = s3 >> 21; s4 += carry3; s3 -= carry3 << 21; + carry4 = s4 >> 21; s5 += carry4; s4 -= carry4 << 21; + carry5 = s5 >> 21; s6 += carry5; s5 -= carry5 << 21; + carry6 = s6 >> 21; s7 += carry6; s6 -= carry6 << 21; + carry7 = s7 >> 21; s8 += carry7; s7 -= carry7 << 21; + carry8 = s8 >> 21; s9 += carry8; s8 -= carry8 << 21; + carry9 = s9 >> 21; s10 += carry9; s9 -= carry9 << 21; + carry10 = s10 >> 21; s11 += carry10; s10 -= carry10 << 21; + carry11 = s11 >> 21; s12 += carry11; s11 -= carry11 << 21; + + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + + carry0 = s0 >> 21; s1 += carry0; s0 -= carry0 << 21; + carry1 = s1 >> 21; s2 += carry1; s1 -= carry1 << 21; + carry2 = s2 >> 21; s3 += carry2; s2 -= carry2 << 21; + carry3 = s3 >> 21; s4 += carry3; s3 -= carry3 << 21; + carry4 = s4 >> 21; s5 += carry4; s4 -= carry4 << 21; + carry5 = s5 >> 21; s6 += carry5; s5 -= carry5 << 21; + carry6 = s6 >> 21; s7 += carry6; s6 -= carry6 << 21; + carry7 = s7 >> 21; s8 += carry7; s7 -= carry7 << 21; + carry8 = s8 >> 21; s9 += carry8; s8 -= carry8 << 21; + carry9 = s9 >> 21; s10 += carry9; s9 -= carry9 << 21; + carry10 = s10 >> 21; s11 += carry10; s10 -= carry10 << 21; + + s[0] = s0 >> 0; + s[1] = s0 >> 8; + s[2] = (s0 >> 16) | (s1 << 5); + s[3] = s1 >> 3; + s[4] = s1 >> 11; + s[5] = (s1 >> 19) | (s2 << 2); + s[6] = s2 >> 6; + s[7] = (s2 >> 14) | (s3 << 7); + s[8] = s3 >> 1; + s[9] = s3 >> 9; + s[10] = (s3 >> 17) | (s4 << 4); + s[11] = s4 >> 4; + s[12] = s4 >> 12; + s[13] = (s4 >> 20) | (s5 << 1); + s[14] = s5 >> 7; + s[15] = (s5 >> 15) | (s6 << 6); + s[16] = s6 >> 2; + s[17] = s6 >> 10; + s[18] = (s6 >> 18) | (s7 << 3); + s[19] = s7 >> 5; + s[20] = s7 >> 13; + s[21] = s8 >> 0; + s[22] = s8 >> 8; + s[23] = (s8 >> 16) | (s9 << 5); + s[24] = s9 >> 3; + s[25] = s9 >> 11; + s[26] = (s9 >> 19) | (s10 << 2); + s[27] = s10 >> 6; + s[28] = (s10 >> 14) | (s11 << 7); + s[29] = s11 >> 1; + s[30] = s11 >> 9; + s[31] = s11 >> 17; +} + +static int64_t signum(int64_t a) { + return a > 0 ? 1 : a < 0 ? -1 : 0; +} + +//! @brief arithmetic left shift for signed operands +static int64_t signed_lshift(const int64_t a, const int b) { +#ifdef __GNUC__ + return a << b; // well-defined in GCC +#else + return a * ((int64_t)1 << b); +#endif +} + +int sc_check(const unsigned char *s) { + int64_t s0 = load_4(s); + int64_t s1 = load_4(s + 4); + int64_t s2 = load_4(s + 8); + int64_t s3 = load_4(s + 12); + int64_t s4 = load_4(s + 16); + int64_t s5 = load_4(s + 20); + int64_t s6 = load_4(s + 24); + int64_t s7 = load_4(s + 28); + return -(0 > + ( signum(1559614444 - s0) + + signed_lshift(signum(1477600026 - s1), 1) + + signed_lshift(signum(2734136534 - s2), 2) + + signed_lshift(signum(350157278 - s3), 3) + + signed_lshift(signum( - s4), 4) + + signed_lshift(signum( - s5), 5) + + signed_lshift(signum( - s6), 6) + + signed_lshift(signum(268435456 - s7), 7) + )); +} + +int sc_isnonzero(const unsigned char *s) { + return (((int) (s[0] | s[1] | s[2] | s[3] | s[4] | s[5] | s[6] | s[7] | s[8] | + s[9] | s[10] | s[11] | s[12] | s[13] | s[14] | s[15] | s[16] | s[17] | + s[18] | s[19] | s[20] | s[21] | s[22] | s[23] | s[24] | s[25] | s[26] | + s[27] | s[28] | s[29] | s[30] | s[31]) - 1) >> 8) + 1; +} + +int ge_p3_is_point_at_infinity_vartime(const ge_p3 *p) { + // https://eprint.iacr.org/2008/522 + // X == T == 0 and Y/Z == 1 + // note: convert all pieces to canonical bytes in case rounding is required (i.e. an element is > q) + // note2: even though T = XY/Z is true for valid point representations (implying it isn't necessary to + // test T == 0), the input to this function might NOT be valid, so we must test T == 0 + char result_X_bytes[32]; + fe_tobytes((unsigned char*)&result_X_bytes, p->X); + + // X != 0 + for (int i = 0; i < 32; ++i) + { + if (result_X_bytes[i]) + return 0; + } + + char result_T_bytes[32]; + fe_tobytes((unsigned char*)&result_T_bytes, p->T); + + // T != 0 + for (int i = 0; i < 32; ++i) + { + if (result_T_bytes[i]) + return 0; + } + + char result_Y_bytes[32]; + char result_Z_bytes[32]; + fe_tobytes((unsigned char*)&result_Y_bytes, p->Y); + fe_tobytes((unsigned char*)&result_Z_bytes, p->Z); + + // Y != Z + for (int i = 0; i < 32; ++i) + { + if (result_Y_bytes[i] != result_Z_bytes[i]) + return 0; + } + + // is Y nonzero? then Y/Z == 1 + for (int i = 0; i < 32; ++i) + { + if (result_Y_bytes[i] != 0) + return 1; + } + + // Y/Z = 0/0 + return 0; +} diff --git a/chain/monero/crypto/cref/crypto-ops.h b/chain/monero/crypto/cref/crypto-ops.h new file mode 100644 index 00000000..c103f1f7 --- /dev/null +++ b/chain/monero/crypto/cref/crypto-ops.h @@ -0,0 +1,169 @@ +// Copyright (c) 2014-2024, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#pragma once + +#include + +/* From fe.h */ + +typedef int32_t fe[10]; + +/* From ge.h */ + +typedef struct { + fe X; + fe Y; + fe Z; +} ge_p2; + +typedef struct { + fe X; + fe Y; + fe Z; + fe T; +} ge_p3; + +typedef struct { + fe X; + fe Y; + fe Z; + fe T; +} ge_p1p1; + +typedef struct { + fe yplusx; + fe yminusx; + fe xy2d; +} ge_precomp; + +typedef struct { + fe YplusX; + fe YminusX; + fe Z; + fe T2d; +} ge_cached; + +/* From ge_add.c */ + +void ge_add(ge_p1p1 *, const ge_p3 *, const ge_cached *); + +/* From ge_double_scalarmult.c, modified */ + +typedef ge_cached ge_dsmp[8]; +extern const ge_precomp ge_Bi[8]; +void ge_dsm_precomp(ge_dsmp r, const ge_p3 *s); +void ge_double_scalarmult_base_vartime(ge_p2 *, const unsigned char *, const ge_p3 *, const unsigned char *); +void ge_triple_scalarmult_base_vartime(ge_p2 *, const unsigned char *, const unsigned char *, const ge_dsmp, const unsigned char *, const ge_dsmp); +void ge_double_scalarmult_base_vartime_p3(ge_p3 *, const unsigned char *, const ge_p3 *, const unsigned char *); + +/* From ge_frombytes.c, modified */ + +extern const fe fe_sqrtm1; +extern const fe fe_d; +int ge_frombytes_vartime(ge_p3 *, const unsigned char *); + +/* From ge_p1p1_to_p2.c */ + +void ge_p1p1_to_p2(ge_p2 *, const ge_p1p1 *); + +/* From ge_p1p1_to_p3.c */ + +void ge_p1p1_to_p3(ge_p3 *, const ge_p1p1 *); + +/* From ge_p2_dbl.c */ + +void ge_p2_dbl(ge_p1p1 *, const ge_p2 *); + +/* From ge_p3_to_cached.c */ + +extern const fe fe_d2; +void ge_p3_to_cached(ge_cached *, const ge_p3 *); + +/* From ge_p3_to_p2.c */ + +void ge_p3_to_p2(ge_p2 *, const ge_p3 *); + +/* From ge_p3_tobytes.c */ + +void ge_p3_tobytes(unsigned char *, const ge_p3 *); + +/* From ge_scalarmult_base.c */ + +extern const ge_precomp ge_base[32][8]; +void ge_scalarmult_base(ge_p3 *, const unsigned char *); + +/* From ge_tobytes.c */ + +void ge_tobytes(unsigned char *, const ge_p2 *); + +/* From sc_reduce.c */ + +void sc_reduce(unsigned char *); + +/* New code */ + +void ge_scalarmult(ge_p2 *, const unsigned char *, const ge_p3 *); +void ge_scalarmult_p3(ge_p3 *, const unsigned char *, const ge_p3 *); +void ge_double_scalarmult_precomp_vartime(ge_p2 *, const unsigned char *, const ge_p3 *, const unsigned char *, const ge_dsmp); +void ge_triple_scalarmult_precomp_vartime(ge_p2 *, const unsigned char *, const ge_dsmp, const unsigned char *, const ge_dsmp, const unsigned char *, const ge_dsmp); +void ge_double_scalarmult_precomp_vartime2(ge_p2 *, const unsigned char *, const ge_dsmp, const unsigned char *, const ge_dsmp); +void ge_double_scalarmult_precomp_vartime2_p3(ge_p3 *, const unsigned char *, const ge_dsmp, const unsigned char *, const ge_dsmp); +void ge_mul8(ge_p1p1 *, const ge_p2 *); +extern const fe fe_ma2; +extern const fe fe_ma; +extern const fe fe_fffb1; +extern const fe fe_fffb2; +extern const fe fe_fffb3; +extern const fe fe_fffb4; +extern const ge_p3 ge_p3_identity; +extern const ge_p3 ge_p3_H; +void ge_fromfe_frombytes_vartime(ge_p2 *, const unsigned char *); +void sc_0(unsigned char *); +void sc_reduce32(unsigned char *); +void sc_add(unsigned char *, const unsigned char *, const unsigned char *); +void sc_sub(unsigned char *, const unsigned char *, const unsigned char *); +void sc_mulsub(unsigned char *, const unsigned char *, const unsigned char *, const unsigned char *); +void sc_mul(unsigned char *, const unsigned char *, const unsigned char *); +void sc_muladd(unsigned char *s, const unsigned char *a, const unsigned char *b, const unsigned char *c); +int sc_check(const unsigned char *); +int sc_isnonzero(const unsigned char *); /* Doesn't normalize */ + +// internal +uint64_t load_3(const unsigned char *in); +uint64_t load_4(const unsigned char *in); +void ge_sub(ge_p1p1 *r, const ge_p3 *p, const ge_cached *q); +void fe_add(fe h, const fe f, const fe g); +void fe_tobytes(unsigned char *, const fe); +void fe_invert(fe out, const fe z); +void fe_mul(fe out, const fe, const fe); +void fe_0(fe h); + +int ge_p3_is_point_at_infinity_vartime(const ge_p3 *p); diff --git a/chain/monero/crypto/cref/libbpplus.a b/chain/monero/crypto/cref/libbpplus.a new file mode 100644 index 00000000..57ba8506 Binary files /dev/null and b/chain/monero/crypto/cref/libbpplus.a differ diff --git a/chain/monero/crypto/cref/monero_crypto.go b/chain/monero/crypto/cref/monero_crypto.go new file mode 100644 index 00000000..2ef997cf --- /dev/null +++ b/chain/monero/crypto/cref/monero_crypto.go @@ -0,0 +1,302 @@ +package cref + +// #cgo CFLAGS: -DNDEBUG +// #cgo LDFLAGS: -L${SRCDIR} -lbpplus -lstdc++ -lsodium +// #include "crypto-ops.h" +// #include "bp_plus_wrapper.h" +// #include +// +// // get_H: return the precomputed H generator point +// void monero_get_H(unsigned char *result) { +// ge_p3_tobytes(result, &ge_p3_H); +// } +// +// // hash_to_ec: Keccak256(data) -> ge_fromfe_frombytes_vartime -> mul8 -> compress +// // This matches Monero's hash_to_ec used for key image computation. +// void monero_hash_to_ec(const unsigned char *pubkey, unsigned char *result) { +// // We receive the already-hashed bytes (Keccak256 output) from Go. +// // Apply ge_fromfe_frombytes_vartime + cofactor multiply. +// ge_p2 point_p2; +// ge_p1p1 point_p1p1; +// ge_p3 point_p3; +// +// ge_fromfe_frombytes_vartime(&point_p2, pubkey); +// +// // Multiply by cofactor 8: p2 -> p1p1 (via mul8) -> p3 -> compress +// ge_mul8(&point_p1p1, &point_p2); +// ge_p1p1_to_p3(&point_p3, &point_p1p1); +// ge_p3_tobytes(result, &point_p3); +// } +// +// // hash_to_point_raw: ge_fromfe_frombytes_vartime WITHOUT cofactor multiply. +// // Used for matching the "hash_to_point" test vectors. +// void monero_hash_to_point_raw(const unsigned char *input, unsigned char *result) { +// ge_p2 point; +// ge_fromfe_frombytes_vartime(&point, input); +// ge_tobytes(result, &point); +// } +// +// // generate_key_derivation: D = 8 * secret * public +// void monero_generate_key_derivation(const unsigned char *pub, const unsigned char *sec, unsigned char *derivation) { +// ge_p3 pub_point; +// ge_p2 tmp2; +// ge_p1p1 tmp1; +// +// ge_frombytes_vartime(&pub_point, pub); +// ge_scalarmult(&tmp2, sec, &pub_point); +// ge_mul8(&tmp1, &tmp2); +// ge_p1p1_to_p2(&tmp2, &tmp1); +// ge_tobytes(derivation, &tmp2); +// } +// +// // derivation_to_scalar: H_s(derivation || varint(output_index)) +// void monero_derivation_to_scalar(const unsigned char *derivation, unsigned int output_index, unsigned char *scalar) { +// // Build buffer: derivation (32 bytes) + varint(output_index) +// unsigned char buf[32 + 10]; // max varint size is 10 +// memcpy(buf, derivation, 32); +// int len = 32; +// unsigned int val = output_index; +// while (val >= 0x80) { +// buf[len++] = (val & 0x7f) | 0x80; +// val >>= 7; +// } +// buf[len++] = val; +// // We'll do the Keccak hash in Go since we already have it there. +// // Just return the raw data for Go to hash. +// memcpy(scalar, buf, len); +// // Store length in the last byte position we can use +// scalar[31] = (unsigned char)len; +// } +// +// // derive_public_key: derived = scalar*G + base +// void monero_derive_public_key(const unsigned char *derivation, unsigned int output_index, const unsigned char *base, unsigned char *derived) { +// // Compute scalar = H_s(derivation || varint(output_index)) +// unsigned char buf[32 + 10]; +// memcpy(buf, derivation, 32); +// int len = 32; +// unsigned int val = output_index; +// while (val >= 0x80) { +// buf[len++] = (val & 0x7f) | 0x80; +// val >>= 7; +// } +// buf[len++] = val; +// +// // The caller will do Keccak in Go and pass us the scalar directly. +// // For this function, we compute scalar*G + base in the C code. +// // But we need the scalar from Go... Let's have Go handle the hash part. +// // This function takes the already-computed scalar. +// ge_p3 base_point; +// ge_p3 result; +// ge_frombytes_vartime(&base_point, base); +// +// // scalar * G +// ge_p3 sG; +// ge_scalarmult_base(&sG, derivation); // reuse derivation as scalar input +// +// // sG + base +// ge_cached base_cached; +// ge_p1p1 sum_p1p1; +// ge_p3_to_cached(&base_cached, &base_point); +// ge_add(&sum_p1p1, &sG, &base_cached); +// ge_p1p1_to_p3(&result, &sum_p1p1); +// ge_p3_tobytes(derived, &result); +// } +// +// // sc_reduce32: reduce a 32-byte value mod L +// void monero_sc_reduce32(unsigned char *s) { +// sc_reduce32(s); +// } +// +// // generate_key_image: I = secret * hash_to_ec(public) +// void monero_generate_key_image(const unsigned char *pub, const unsigned char *sec, unsigned char *image) { +// ge_p2 hp_p2; +// ge_p1p1 hp_p1p1; +// ge_p3 hp; +// ge_p2 result; +// +// // hash_to_ec(pub) = cofactor * ge_fromfe_frombytes_vartime(Keccak(pub)) +// // The caller passes Keccak(pub) as `pub` here. +// ge_fromfe_frombytes_vartime(&hp_p2, pub); +// ge_mul8(&hp_p1p1, &hp_p2); +// ge_p1p1_to_p3(&hp, &hp_p1p1); +// +// // I = sec * hp +// ge_scalarmult(&result, sec, &hp); +// ge_tobytes(image, &result); +// } +import "C" +import ( + "fmt" + "unsafe" +) + +// GetH returns the precomputed H generator point used for Pedersen commitments. +func GetH() [32]byte { + var result [32]byte + C.monero_get_H((*C.uchar)(unsafe.Pointer(&result[0]))) + return result +} + +// HashToEC computes Monero's hash_to_ec: ge_fromfe_frombytes_vartime(input) * 8 +// Input should be the Keccak256 hash of the public key. +func HashToEC(keccakHash []byte) [32]byte { + var result [32]byte + C.monero_hash_to_ec( + (*C.uchar)(unsafe.Pointer(&keccakHash[0])), + (*C.uchar)(unsafe.Pointer(&result[0])), + ) + return result +} + +// HashToPointRaw computes ge_fromfe_frombytes_vartime WITHOUT cofactor multiply. +// This matches the "hash_to_point" test vectors in Monero's test suite. +func HashToPointRaw(input []byte) [32]byte { + var result [32]byte + C.monero_hash_to_point_raw( + (*C.uchar)(unsafe.Pointer(&input[0])), + (*C.uchar)(unsafe.Pointer(&result[0])), + ) + return result +} + +// GenerateKeyDerivation computes D = 8 * secret * public (compressed output). +func GenerateKeyDerivation(pub, sec []byte) [32]byte { + var result [32]byte + C.monero_generate_key_derivation( + (*C.uchar)(unsafe.Pointer(&pub[0])), + (*C.uchar)(unsafe.Pointer(&sec[0])), + (*C.uchar)(unsafe.Pointer(&result[0])), + ) + return result +} + +// ScReduce32 reduces a 32-byte value mod the ed25519 group order L. +func ScReduce32(s []byte) [32]byte { + var result [32]byte + copy(result[:], s) + C.monero_sc_reduce32((*C.uchar)(unsafe.Pointer(&result[0]))) + return result +} + +// GenerateKeyImage computes I = secret * hash_to_ec(Keccak(public)) +// keccakPub should be Keccak256(public_key), sec is the secret scalar. +func GenerateKeyImage(keccakPub, sec []byte) [32]byte { + var result [32]byte + C.monero_generate_key_image( + (*C.uchar)(unsafe.Pointer(&keccakPub[0])), + (*C.uchar)(unsafe.Pointer(&sec[0])), + (*C.uchar)(unsafe.Pointer(&result[0])), + ) + return result +} + +// BPPlusProve generates a Bulletproofs+ range proof using Monero's exact C++ implementation. +// amounts: slice of uint64 values to prove range for +// masks: slice of 32-byte blinding factors (one per amount) +// Returns the serialized proof bytes. +func BPPlusProve(amounts []uint64, masks [][]byte) ([]byte, error) { + if len(amounts) == 0 || len(amounts) != len(masks) { + return nil, fmt.Errorf("invalid BP+ input: %d amounts, %d masks", len(amounts), len(masks)) + } + count := len(amounts) + + // Flatten masks into contiguous byte array + flatMasks := make([]byte, count*32) + for i, m := range masks { + if len(m) != 32 { + return nil, fmt.Errorf("mask %d is %d bytes, expected 32", i, len(m)) + } + copy(flatMasks[i*32:], m) + } + + // Output buffer + proofBuf := make([]byte, 8192) + proofLen := C.int(len(proofBuf)) + + ret := C.monero_bp_plus_prove( + (*C.uint64_t)(unsafe.Pointer(&amounts[0])), + (*C.uchar)(unsafe.Pointer(&flatMasks[0])), + C.int(count), + (*C.uchar)(unsafe.Pointer(&proofBuf[0])), + &proofLen, + ) + if ret != 0 { + return nil, fmt.Errorf("BP+ prove failed with code %d", ret) + } + + return proofBuf[:proofLen], nil +} + +// BPPlusProveRaw is a lower-level version that returns the raw proof and the V commitments separately. +// Returns (V commitments as [][]byte, proof fields as raw bytes, error) +func BPPlusProveRaw(amounts []uint64, masks [][]byte) ([][]byte, BPPlusFields, error) { + raw, err := BPPlusProve(amounts, masks) + if err != nil { + return nil, BPPlusFields{}, err + } + return ParseBPPlusProof(raw) +} + +// BPPlusFields contains the parsed fields of a BP+ proof. +type BPPlusFields struct { + A, A1, B [32]byte + R1, S1, D1 [32]byte + L [][32]byte + R [][32]byte +} + +// ParseBPPlusProof parses the serialized proof from the C wrapper. +// Returns (V commitments, proof fields, error). +func ParseBPPlusProof(raw []byte) ([][]byte, BPPlusFields, error) { + var fields BPPlusFields + pos := 0 + + readU32 := func() uint32 { + v := uint32(raw[pos]) | uint32(raw[pos+1])<<8 | uint32(raw[pos+2])<<16 | uint32(raw[pos+3])<<24 + pos += 4 + return v + } + readKey := func() [32]byte { + var k [32]byte + copy(k[:], raw[pos:pos+32]) + pos += 32 + return k + } + + nV := int(readU32()) + V := make([][]byte, nV) + for i := 0; i < nV; i++ { + k := readKey() + V[i] = k[:] + } + + fields.A = readKey() + fields.A1 = readKey() + fields.B = readKey() + fields.R1 = readKey() + fields.S1 = readKey() + fields.D1 = readKey() + + nL := int(readU32()) + fields.L = make([][32]byte, nL) + for i := 0; i < nL; i++ { + fields.L[i] = readKey() + } + + nR := int(readU32()) + fields.R = make([][32]byte, nR) + for i := 0; i < nR; i++ { + fields.R[i] = readKey() + } + + return V, fields, nil +} + +// BPPlusVerify verifies a Bulletproofs+ range proof using Monero's exact C++ implementation. +func BPPlusVerify(proof []byte) bool { + ret := C.monero_bp_plus_verify( + (*C.uchar)(unsafe.Pointer(&proof[0])), + C.int(len(proof)), + ) + return ret == 1 +} diff --git a/chain/monero/crypto/cref/monero_crypto_test.go b/chain/monero/crypto/cref/monero_crypto_test.go new file mode 100644 index 00000000..8a146b6b --- /dev/null +++ b/chain/monero/crypto/cref/monero_crypto_test.go @@ -0,0 +1,115 @@ +package cref + +import ( + "encoding/hex" + "testing" + + "github.com/stretchr/testify/require" + "golang.org/x/crypto/sha3" +) + +func keccak256(data []byte) []byte { + h := sha3.NewLegacyKeccak256() + h.Write(data) + return h.Sum(nil) +} + +func hexDecode(t *testing.T, s string) []byte { + b, err := hex.DecodeString(s) + require.NoError(t, err) + return b +} + +// Test vectors from monero-project/monero/tests/crypto/tests.txt +// Format: hash_to_point +func TestHashToPointRaw(t *testing.T) { + vectors := []struct { + input string + expected string + }{ + {"83efb774657700e37291f4b8dd10c839d1c739fd135c07a2fd7382334dafdd6a", "2789ecbaf36e4fcb41c6157228001538b40ca379464b718d830c58caae7ea4ca"}, + {"5c380f98794ab7a9be7c2d3259b92772125ce93527be6a76210631fdd8001498", "31a1feb4986d42e2137ae061ea031838d24fa523234954cf8860bcd42421ae94"}, + {"4775d39f91a466262f0ccf21f5a7ee446f79a05448861e212be063a1063298f0", "897b3589f29ea40e576a91506d9aeca4c05a494922a80de57276f4b40c0a98bc"}, + {"e11135e56c57a95cf2e668183e91cfed3122e0bb80e833522d4dda335b57c8ff", "d52757c2bfdd30bf4137d66c087b07486643938c32d6aae0b88d20aa3c07c594"}, + {"3f287e7e6cf6ef2ed9a8c7361e4ec96535f0df208ddee9a57ffb94d4afb94a93", "e462eea6e7d404b0f1219076e3433c742a1641dbcc9146362c27d152c6175410"}, + } + + for _, v := range vectors { + input := hexDecode(t, v.input) + result := HashToPointRaw(input) + require.Equal(t, v.expected, hex.EncodeToString(result[:]), "hash_to_point(%s)", v.input) + } +} + +// Test vectors: hash_to_ec +// hash_to_ec = hash_to_point(Keccak256(pubkey)) * 8 +func TestHashToEC(t *testing.T) { + vectors := []struct { + pubkey string + expected string + }{ + {"da66e9ba613919dec28ef367a125bb310d6d83fb9052e71034164b6dc4f392d0", "52b3f38753b4e13b74624862e253072cf12f745d43fcfafbe8c217701a6e5875"}, + {"a7fbdeeccb597c2d5fdaf2ea2e10cbfcd26b5740903e7f6d46bcbf9a90384fc6", "f055ba2d0d9828ce2e203d9896bfda494d7830e7e3a27fa27d5eaa825a79a19c"}, + {"ed6e6579368caba2cc4851672972e949c0ee586fee4d6d6a9476d4a908f64070", "da3ceda9a2ef6316bf9272566e6dffd785ac71f57855c0202f422bbb86af4ec0"}, + {"9ae78e5620f1c4e6b29d03da006869465b3b16dae87ab0a51f4e1b74bc8aa48b", "72d8720da66f797f55fbb7fa538af0b4a4f5930c8289c991472c37dc5ec16853"}, + {"ab49eb4834d24db7f479753217b763f70604ecb79ed37e6c788528720f424e5b", "45914ba926a1a22c8146459c7f050a51ef5f560f5b74bae436b93a379866e6b8"}, + } + + for _, v := range vectors { + pubkey := hexDecode(t, v.pubkey) + kHash := keccak256(pubkey) + result := HashToEC(kHash) + require.Equal(t, v.expected, hex.EncodeToString(result[:]), "hash_to_ec(%s)", v.pubkey) + } +} + +// Test vectors: generate_key_derivation true +func TestGenerateKeyDerivation(t *testing.T) { + vectors := []struct { + pub string + sec string + derivation string + }{ + {"fdfd97d2ea9f1c25df773ff2c973d885653a3ee643157eb0ae2b6dd98f0b6984", "eb2bd1cf0c5e074f9dbf38ebbc99c316f54e21803048c687a3bb359f7a713b02", "4e0bd2c41325a1b89a9f7413d4d05e0a5a4936f241dccc3c7d0c539ffe00ef67"}, + {"1ebf8c3c296bb91708b09d9a8e0639ccfd72556976419c7dc7e6dfd7599218b9", "e49f363fd5c8fc1f8645983647ca33d7ec9db2d255d94cd538a3cc83153c5f04", "72903ec8f9919dfcec6efb5535490527b573b3d77f9890386d373c02bf368934"}, + {"3e3047a633b1f84250ae11b5c8e8825a3df4729f6cbe4713b887db62f268187d", "6df324e24178d91c640b75ab1c6905f8e6bb275bc2c2a5d9b9ecf446765a5a05", "9dcac9c9e87dd96a4115d84d587218d8bf165a0527153b1c306e562fe39a46ab"}, + } + + for _, v := range vectors { + pub := hexDecode(t, v.pub) + sec := hexDecode(t, v.sec) + result := GenerateKeyDerivation(pub, sec) + require.Equal(t, v.derivation, hex.EncodeToString(result[:]), "generate_key_derivation(%s, %s)", v.pub, v.sec) + } +} + +// Test vectors: generate_key_image +func TestGenerateKeyImage(t *testing.T) { + vectors := []struct { + pub string + sec string + image string + }{ + {"e46b60ebfe610b8ba761032018471e5719bb77ea1cd945475c4a4abe7224bfd0", "981d477fb18897fa1f784c89721a9d600bf283f06b89cb018a077f41dcefef0f", "a637203ec41eab772532d30420eac80612fce8e44f1758bc7e2cb1bdda815887"}, + {"8661153f5f856b46f83e9e225777656cd95584ab16396fa03749ec64e957283b", "156d7f2e20899371404b87d612c3587ffe9fba294bafbbc99bb1695e3275230e", "03ec63d7f1b722f551840b2725c76620fa457c805cbbf2ee941a6bf4cfb6d06c"}, + {"30216ae687676a89d84bf2a333feeceb101707193a9ee7bcbb47d54268e6cc83", "1b425ba4b8ead10f7f7c0c923ec2e6847e77aa9c7e9a880e89980178cb02fa0c", "4f675ce3a8dfd806b7c4287c19d741f51141d3fce3e3a3d1be8f3f449c22dd19"}, + } + + for _, v := range vectors { + pub := hexDecode(t, v.pub) + sec := hexDecode(t, v.sec) + kHash := keccak256(pub) + result := GenerateKeyImage(kHash, sec) + require.Equal(t, v.image, hex.EncodeToString(result[:]), "generate_key_image(%s, %s)", v.pub, v.sec) + } +} + +func TestScReduce32(t *testing.T) { + // A value that's >= L should be reduced + // L = 2^252 + 27742317777372353535851937790883648493 + // Test with a value we know is valid from the test vectors + input := hexDecode(t, "ac10e070c8574ef374bdd1c5dbe9bacfd927f9ae0705cf08018ff865f6092d0f") + result := ScReduce32(input) + // For values < L, sc_reduce32 is identity + require.Equal(t, hex.EncodeToString(input), hex.EncodeToString(result[:])) +} diff --git a/chain/monero/crypto/crypto_test.go b/chain/monero/crypto/crypto_test.go new file mode 100644 index 00000000..1665216c --- /dev/null +++ b/chain/monero/crypto/crypto_test.go @@ -0,0 +1,250 @@ +package crypto + +import ( + "encoding/hex" + "testing" + + "filippo.io/edwards25519" + "github.com/stretchr/testify/require" +) + +func hexDec(t *testing.T, s string) []byte { + b, err := hex.DecodeString(s) + require.NoError(t, err) + return b +} + +// Test vectors from monero-project/tests/crypto/tests.txt +func TestHashToEC(t *testing.T) { + vectors := []struct{ input, expected string }{ + {"da66e9ba613919dec28ef367a125bb310d6d83fb9052e71034164b6dc4f392d0", "52b3f38753b4e13b74624862e253072cf12f745d43fcfafbe8c217701a6e5875"}, + {"a7fbdeeccb597c2d5fdaf2ea2e10cbfcd26b5740903e7f6d46bcbf9a90384fc6", "f055ba2d0d9828ce2e203d9896bfda494d7830e7e3a27fa27d5eaa825a79a19c"}, + {"9ae78e5620f1c4e6b29d03da006869465b3b16dae87ab0a51f4e1b74bc8aa48b", "72d8720da66f797f55fbb7fa538af0b4a4f5930c8289c991472c37dc5ec16853"}, + } + for _, v := range vectors { + result := HashToEC(hexDec(t, v.input)) + require.Equal(t, v.expected, hex.EncodeToString(result), "hash_to_ec(%s)", v.input) + } +} + +func TestGenerateKeyDerivation(t *testing.T) { + vectors := []struct{ pub, sec, expected string }{ + {"fdfd97d2ea9f1c25df773ff2c973d885653a3ee643157eb0ae2b6dd98f0b6984", "eb2bd1cf0c5e074f9dbf38ebbc99c316f54e21803048c687a3bb359f7a713b02", "4e0bd2c41325a1b89a9f7413d4d05e0a5a4936f241dccc3c7d0c539ffe00ef67"}, + {"1ebf8c3c296bb91708b09d9a8e0639ccfd72556976419c7dc7e6dfd7599218b9", "e49f363fd5c8fc1f8645983647ca33d7ec9db2d255d94cd538a3cc83153c5f04", "72903ec8f9919dfcec6efb5535490527b573b3d77f9890386d373c02bf368934"}, + {"3e3047a633b1f84250ae11b5c8e8825a3df4729f6cbe4713b887db62f268187d", "6df324e24178d91c640b75ab1c6905f8e6bb275bc2c2a5d9b9ecf446765a5a05", "9dcac9c9e87dd96a4115d84d587218d8bf165a0527153b1c306e562fe39a46ab"}, + } + for _, v := range vectors { + result, err := GenerateKeyDerivation(hexDec(t, v.pub), hexDec(t, v.sec)) + require.NoError(t, err) + require.Equal(t, v.expected, hex.EncodeToString(result)) + } +} + +// Test vectors from monero-project/tests/crypto/tests.txt +func TestGenerateKeyImage(t *testing.T) { + vectors := []struct{ pub, sec, expected string }{ + {"e46b60ebfe610b8ba761032018471e5719bb77ea1cd945475c4a4abe7224bfd0", "981d477fb18897fa1f784c89721a9d600bf283f06b89cb018a077f41dcefef0f", "a637203ec41eab772532d30420eac80612fce8e44f1758bc7e2cb1bdda815887"}, + {"8661153f5f856b46f83e9e225777656cd95584ab16396fa03749ec64e957283b", "156d7f2e20899371404b87d612c3587ffe9fba294bafbbc99bb1695e3275230e", "03ec63d7f1b722f551840b2725c76620fa457c805cbbf2ee941a6bf4cfb6d06c"}, + {"30216ae687676a89d84bf2a333feeceb101707193a9ee7bcbb47d54268e6cc83", "1b425ba4b8ead10f7f7c0c923ec2e6847e77aa9c7e9a880e89980178cb02fa0c", "4f675ce3a8dfd806b7c4287c19d741f51141d3fce3e3a3d1be8f3f449c22dd19"}, + } + for _, v := range vectors { + secScalar, _ := edwards25519.NewScalar().SetCanonicalBytes(hexDec(t, v.sec)) + pubPoint, _ := edwards25519.NewIdentityPoint().SetBytes(hexDec(t, v.pub)) + ki := ComputeKeyImage(secScalar, pubPoint) + require.Equal(t, v.expected, hex.EncodeToString(ki.Bytes())) + } +} + +func TestHGeneratorPoint(t *testing.T) { + // H is a precomputed constant from Monero's crypto-ops-data.c + require.Equal(t, "8b655970153799af2aeadc9ff1add0ea6c7251d54154cfa92c173a0dd39c1f94", + hex.EncodeToString(H.Bytes())) +} + +func TestFixedViewKey(t *testing.T) { + // Fixed view key is deterministic from seed + expected := ScReduce32(Keccak256([]byte("crosschain_monero_view_key"))) + require.Equal(t, hex.EncodeToString(expected), hex.EncodeToString(FixedPrivateViewKey)) + + // Calling DeriveViewKey with ANY spend key returns the fixed view key + randomSpend := ScReduce32(Keccak256([]byte("random spend key"))) + viewKey := DeriveViewKey(randomSpend) + require.Equal(t, hex.EncodeToString(FixedPrivateViewKey), hex.EncodeToString(viewKey)) +} + +func TestDeriveKeysAndAddress(t *testing.T) { + seed := hexDec(t, "c071fe9b1096538b047087a4ee3fdae204e4682eb2dfab78f3af752704b0f700") + privSpend, privView, pubSpend, pubView, err := DeriveKeysFromSpend(seed) + require.NoError(t, err) + require.Len(t, privSpend, 32) + require.Len(t, privView, 32) + require.Len(t, pubSpend, 32) + require.Len(t, pubView, 32) + + // View key should be the fixed key, not derived from spend + require.Equal(t, hex.EncodeToString(FixedPrivateViewKey), hex.EncodeToString(privView)) + + // Address roundtrip + addr := GenerateAddress(pubSpend, pubView) + require.True(t, addr[0] == '4', "mainnet address starts with 4") + require.Len(t, addr, 95) + + prefix, decodedSpend, decodedView, err := DecodeAddress(addr) + require.NoError(t, err) + require.Equal(t, MainnetAddressPrefix, prefix) + require.Equal(t, hex.EncodeToString(pubSpend), hex.EncodeToString(decodedSpend)) + require.Equal(t, hex.EncodeToString(pubView), hex.EncodeToString(decodedView)) + + // Testnet address + testAddr := GenerateAddressWithPrefix(TestnetAddressPrefix, pubSpend, pubView) + require.True(t, testAddr[0] == '9', "testnet address starts with 9") +} + +func TestTransactionHashThreeHash(t *testing.T) { + // Verified against real Monero testnet transaction + // TX hash: 197d45b6a07c9ccafb7cf8e5f72c18edff1c294f1490b7dd34c8d3ff0e669814 + // The three-hash structure: H(H(prefix) || H(rct_base) || H(rct_prunable)) + prefixHash := hexDec(t, "3ba6d564a24994fe8e7ca5553f5b8cded5cddbee8dd7210f4089a89dbbeb3d0f") + rctBaseHash := hexDec(t, "609bb65a0e2c02f10f01cc09dc658771aae5f7c019f2375532a15887b8497b3d") + prunableHash := hexDec(t, "98228265520f9883f37590d032949760c4176c7ea0c4ae3318c06f063d9cfa75") + + combined := make([]byte, 0, 96) + combined = append(combined, prefixHash...) + combined = append(combined, rctBaseHash...) + combined = append(combined, prunableHash...) + txHash := Keccak256(combined) + + require.Equal(t, "197d45b6a07c9ccafb7cf8e5f72c18edff1c294f1490b7dd34c8d3ff0e669814", + hex.EncodeToString(txHash)) +} + +func TestCommitmentMaskDerivation(t *testing.T) { + // Verified against on-chain commitment for our mainnet deposit + // TX: 2ed8ca963cbf3da3a8877f63d59de1d1e2055550b7c797d9f1616b5de36da10b + // Output 0, amount: 43910000000 piconero + // On-chain commitment: 9fae8afbe54a317a6674c06e969b93cc3944d5bc433d98a373b42294087790c7 + + // Keys (from our mainnet wallet with the OLD view key derivation) + privViewOld := hexDec(t, "639bd5b7bbbf6d0b935586331c4b9447a18d9e1450862b25ed72b5764299050b") + txPubKey := hexDec(t, "4efc54aa09b0c7ff00e1be9628650f0ce53ffdb22c29e201a4be128ef53fa36c") + + // Derivation + derivation, err := GenerateKeyDerivation(txPubKey, privViewOld) + require.NoError(t, err) + + // Shared scalar + scalar, err := DerivationToScalar(derivation, 0) + require.NoError(t, err) + + // Commitment mask = H_s("commitment_mask" || scalar) + data := append([]byte("commitment_mask"), scalar...) + mask := ScReduce32(Keccak256(data)) + + // Pedersen commitment: C = amount*H + mask*G + amount := uint64(43910000000) + commitment, err := PedersenCommit(amount, mask) + require.NoError(t, err) + + require.Equal(t, "9fae8afbe54a317a6674c06e969b93cc3944d5bc433d98a373b42294087790c7", + hex.EncodeToString(commitment.Bytes())) +} + +func TestOutputKeyDerivationRoundtrip(t *testing.T) { + // Test that output key derivation and scanning produce consistent results. + // Builder derives: P = H_s(8*r*pubView || idx)*G + pubSpend + // Scanner checks: P == H_s(8*viewKey*R || idx)*G + pubSpend + // Where R = r*G + + privView := FixedPrivateViewKey + pubView, _ := PublicFromPrivate(privView) + + privSpend := ScReduce32(Keccak256([]byte("test spend key"))) + pubSpend, _ := PublicFromPrivate(privSpend) + + // Simulate builder: random tx key + txPrivKey := ScReduce32(Keccak256([]byte("test tx key"))) + txPrivScalar, _ := edwards25519.NewScalar().SetCanonicalBytes(txPrivKey) + txPubKey := edwards25519.NewGeneratorPoint().ScalarBaseMult(txPrivScalar) + + // Builder output derivation (with cofactor) + D, _ := GenerateKeyDerivation(pubView, txPrivKey) + scalar, _ := DerivationToScalar(D, 0) + sScalar, _ := edwards25519.NewScalar().SetCanonicalBytes(scalar) + sG := edwards25519.NewGeneratorPoint().ScalarBaseMult(sScalar) + pubSpendPoint, _ := edwards25519.NewIdentityPoint().SetBytes(pubSpend) + outputKey := edwards25519.NewIdentityPoint().Add(sG, pubSpendPoint) + + // Scanner derivation (with cofactor) + D2, _ := GenerateKeyDerivation(txPubKey.Bytes(), privView) + scalar2, _ := DerivationToScalar(D2, 0) + sScalar2, _ := edwards25519.NewScalar().SetCanonicalBytes(scalar2) + sG2 := edwards25519.NewGeneratorPoint().ScalarBaseMult(sScalar2) + expectedKey := edwards25519.NewIdentityPoint().Add(sG2, pubSpendPoint) + + require.Equal(t, 1, outputKey.Equal(expectedKey), "builder output key must match scanner derivation") + + // Amount encryption roundtrip + amount := uint64(1000000000) + amountBytes := make([]byte, 8) + for i := 0; i < 8; i++ { + amountBytes[i] = byte(amount >> (8 * i)) + } + encKey := Keccak256(append([]byte("amount"), scalar...)) + encrypted := make([]byte, 8) + for i := 0; i < 8; i++ { + encrypted[i] = amountBytes[i] ^ encKey[i] + } + decKey := Keccak256(append([]byte("amount"), scalar2...)) + decrypted := make([]byte, 8) + for i := 0; i < 8; i++ { + decrypted[i] = encrypted[i] ^ decKey[i] + } + decAmount := uint64(0) + for i := 0; i < 8; i++ { + decAmount |= uint64(decrypted[i]) << (8 * i) + } + require.Equal(t, amount, decAmount, "amount must survive encrypt/decrypt roundtrip") + + // Commitment mask roundtrip + maskData := append([]byte("commitment_mask"), scalar...) + mask1 := ScReduce32(Keccak256(maskData)) + maskData2 := append([]byte("commitment_mask"), scalar2...) + mask2 := ScReduce32(Keccak256(maskData2)) + require.Equal(t, hex.EncodeToString(mask1), hex.EncodeToString(mask2), + "commitment mask must be same from builder and scanner derivation") +} + +func TestBulletproofsPlusProveAndVerify(t *testing.T) { + // Single output + amount := uint64(1000000000) // 0.001 XMR + mask := ScReduce32(Keccak256([]byte("test mask"))) + + proof, err := BPPlusProvePureGo([]uint64{amount}, [][]byte{mask}) + require.NoError(t, err) + require.True(t, len(proof) > 500, "proof should be ~620 bytes, got %d", len(proof)) + + // Parse proof fields + _, fields, err := ParseBPPlusProofGo(proof) + require.NoError(t, err) + require.Equal(t, 6, len(fields.L), "single output: 6 L rounds (log2(64))") + require.Equal(t, 6, len(fields.R), "single output: 6 R rounds") + + // Two outputs + mask2 := ScReduce32(Keccak256([]byte("test mask 2"))) + proof2, err := BPPlusProvePureGo([]uint64{500000000, 500000000}, [][]byte{mask, mask2}) + require.NoError(t, err) + + _, fields2, err := ParseBPPlusProofGo(proof2) + require.NoError(t, err) + require.Equal(t, 7, len(fields2.L), "two outputs: 7 L rounds (log2(128))") +} + +func TestScalarReduce(t *testing.T) { + // Values < L pass through + small := hexDec(t, "0100000000000000000000000000000000000000000000000000000000000000") + require.Equal(t, hex.EncodeToString(small), hex.EncodeToString(ScalarReduce(small))) + + // Deterministic + hash := Keccak256([]byte("test")) + require.Equal(t, hex.EncodeToString(ScalarReduce(hash)), hex.EncodeToString(ScalarReduce(hash))) +} diff --git a/chain/monero/crypto/generators.go b/chain/monero/crypto/generators.go new file mode 100644 index 00000000..bdd61a99 --- /dev/null +++ b/chain/monero/crypto/generators.go @@ -0,0 +1,114 @@ +package crypto + +import ( + "filippo.io/edwards25519" +) + +// Generator points for Pedersen commitments and Bulletproofs+. +// +// G = Ed25519 base point (blinding factors) +// H = hash_to_ec(G_compressed) (amount commitments) +// C = v*H + r*G + +const ( + maxN = 64 // bits in range proof + maxM = 16 // max aggregated outputs + maxMN = maxN * maxM +) + +var H *edwards25519.Point +var Gi [maxMN]*edwards25519.Point +var Hi [maxMN]*edwards25519.Point + +func init() { + ensureHtpInit() // must be called before using HashToECPureGo + + // H is a precomputed constant from Monero's crypto-ops-data.c + H, _ = edwards25519.NewIdentityPoint().SetBytes(GetHPureGo()) + + // Gi and Hi vectors for BP+ using Monero's get_exponent: + // get_exponent(H, idx) = hash_to_p3(cn_fast_hash(H || "bulletproof_plus" || varint(idx))) + // hash_to_p3(k) = ge_fromfe(cn_fast_hash(k)) * 8 -- DOUBLE hash! + hBytes := H.Bytes() + bpExponent := []byte("bulletproof_plus") + for i := 0; i < maxMN; i++ { + hiInput := append(append([]byte{}, hBytes...), bpExponent...) + hiInput = append(hiInput, varintEncode(uint64(2*i))...) + giInput := append(append([]byte{}, hBytes...), bpExponent...) + giInput = append(giInput, varintEncode(uint64(2*i+1))...) + + // First hash (cn_fast_hash of the concatenated input) + hiHash1 := Keccak256(hiInput) + giHash1 := Keccak256(giInput) + // Second hash (hash_to_p3 internally hashes again) + hiHash2 := Keccak256(hiHash1) + giHash2 := Keccak256(giHash1) + // Elligator map + cofactor + hiPoint := geFromfeFrombytesVartime(hiHash2) + giPoint := geFromfeFrombytesVartime(giHash2) + hi2 := edwards25519.NewIdentityPoint().Add(hiPoint, hiPoint) + hi4 := edwards25519.NewIdentityPoint().Add(hi2, hi2) + Hi[i] = edwards25519.NewIdentityPoint().Add(hi4, hi4) + gi2 := edwards25519.NewIdentityPoint().Add(giPoint, giPoint) + gi4 := edwards25519.NewIdentityPoint().Add(gi2, gi2) + Gi[i] = edwards25519.NewIdentityPoint().Add(gi4, gi4) + } +} + +// HashToEC computes Monero's hash_to_ec: +// Keccak256(data) -> ge_fromfe_frombytes_vartime -> multiply by cofactor 8 -> compress +func HashToEC(data []byte) []byte { + return HashToECPureGo(data) +} + +// HashToPoint computes ge_fromfe_frombytes_vartime WITHOUT cofactor multiply. +func HashToPoint(data []byte) []byte { + return HashToPointPureGo(data).Bytes() +} + +// ScReduce32 reduces a 32-byte value mod the ed25519 group order L. +func ScReduce32(s []byte) []byte { + return ScReduce32PureGo(s) +} + +// PedersenCommit computes C = v*H + r*G +func PedersenCommit(amount uint64, mask []byte) (*edwards25519.Point, error) { + vBytes := ScalarFromUint64(amount) + vScalar, err := edwards25519.NewScalar().SetCanonicalBytes(vBytes) + if err != nil { + return nil, err + } + vH := edwards25519.NewIdentityPoint().ScalarMult(vScalar, H) + + rScalar, err := edwards25519.NewScalar().SetCanonicalBytes(mask) + if err != nil { + return nil, err + } + rG := edwards25519.NewGeneratorPoint().ScalarBaseMult(rScalar) + + return edwards25519.NewIdentityPoint().Add(vH, rG), nil +} + +// ScalarFromUint64 converts uint64 to 32-byte LE scalar. +func ScalarFromUint64(v uint64) []byte { + b := make([]byte, 32) + for i := 0; i < 8; i++ { + b[i] = byte(v >> (8 * i)) + } + return b +} + +// RandomScalar generates a random scalar mod L using Keccak256 of entropy. +func RandomScalar(entropy []byte) []byte { + hash := Keccak256(entropy) + return ScReduce32(hash) +} + +// BPPlusProveNative generates a BP+ proof using pure Go. +func BPPlusProveNative(amounts []uint64, masks [][]byte) (commitments [][]byte, proofFields BPPlusFields, err error) { + raw, err := BPPlusProvePureGo(amounts, masks) + if err != nil { + return nil, BPPlusFields{}, err + } + return ParseBPPlusProofGo(raw) +} diff --git a/chain/monero/crypto/hash_to_point.go b/chain/monero/crypto/hash_to_point.go new file mode 100644 index 00000000..17f3c471 --- /dev/null +++ b/chain/monero/crypto/hash_to_point.go @@ -0,0 +1,261 @@ +package crypto + +import ( + "filippo.io/edwards25519" + "filippo.io/edwards25519/field" +) + +// Precomputed field element constants for ge_fromfe_frombytes_vartime. +// These match Monero's crypto-ops-data.c values. +var ( + feMA field.Element + feMA2 field.Element + feSqrtM1 field.Element + feFFfb1 field.Element + feFFfb2 field.Element + feFFfb3 field.Element + feFFfb4 field.Element + htpInitDone bool +) + +// ensureHtpInit lazily initializes the Elligator constants. +// Called before first use to avoid init() ordering issues. +func ensureHtpInit() { + if htpInitDone { + return + } + htpInitDone = true + initHtpConstants() +} + +func initHtpConstants() { + // Montgomery A = 486662 + var feA, feTwo, feAp2 field.Element + feA.SetBytes(uint64ToLE(486662)) + feTwo.SetBytes(uint64ToLE(2)) + + // fe_ma = -A + feMA.Negate(&feA) + + // fe_ma2 = -A^2 (NOT -2*A^2; the factor 2 comes from v in the Elligator map) + var aSq field.Element + aSq.Square(&feA) + feMA2.Negate(&aSq) + + // A + 2 + feAp2.Add(&feA, &feTwo) + + // sqrt(-1) computed via SqrtRatio(-1, 1) + var negOne field.Element + negOne.Negate(new(field.Element).One()) + feSqrtM1.SqrtRatio(&negOne, new(field.Element).One()) + + // Compute the fffb constants using SqrtRatio + var one field.Element + one.One() + var aAp2 field.Element + aAp2.Multiply(&feA, &feAp2) // A * (A+2) + + var neg2aAp2, pos2aAp2 field.Element + pos2aAp2.Multiply(&feTwo, &aAp2) // 2*A*(A+2) + neg2aAp2.Negate(&pos2aAp2) // -2*A*(A+2) + + feFFfb1.SqrtRatio(&neg2aAp2, &one) // sqrt(-2*A*(A+2)) + feFFfb2.SqrtRatio(&pos2aAp2, &one) // sqrt(2*A*(A+2)) + + var negSqrtM1aAp2, sqrtM1aAp2 field.Element + sqrtM1aAp2.Multiply(&feSqrtM1, &aAp2) // sqrt(-1)*A*(A+2) + negSqrtM1aAp2.Negate(&sqrtM1aAp2) // -sqrt(-1)*A*(A+2) + feFFfb3.SqrtRatio(&negSqrtM1aAp2, &one) // sqrt(-sqrt(-1)*A*(A+2)) + feFFfb4.SqrtRatio(&sqrtM1aAp2, &one) // sqrt(sqrt(-1)*A*(A+2)) +} + +// geFromfeFrombytesVartime implements Monero's ge_fromfe_frombytes_vartime. +// Maps a 32-byte hash to an Edwards curve point (in projective X:Y:Z coordinates). +// This is NOT the standard Ed25519 point decompression - it's an Elligator-like map. +func geFromfeFrombytesVartime(s []byte) *edwards25519.Point { + ensureHtpInit() + var u, v, w, x, y, z field.Element + + // u = field element from bytes. + // Monero's fe_frombytes does NOT reduce mod p or clear the high bit. + // field.Element.SetBytes clears bit 255. We add 2^255 back if needed. + highBit := (s[31] >> 7) & 1 + u.SetBytes(s) + if highBit == 1 { + // Add 2^255 = 19 (mod p, since p = 2^255 - 19) + // Actually 2^255 mod p = 19, so we add 19 + var nineteen field.Element + nineteen.SetBytes(uint64ToLE(19)) + u.Add(&u, &nineteen) + } + + // v = 2 * u^2 + v.Square(&u) + var two field.Element + two.Add(new(field.Element).One(), new(field.Element).One()) + v.Multiply(&v, &two) + + // w = 2*u^2 + 1 + w.Add(&v, new(field.Element).One()) + + // x = w^2 - 2*A^2*u^2 + x.Square(&w) + var ma2v field.Element + ma2v.Multiply(&feMA2, &v) + x.Add(&x, &ma2v) // x = w^2 + (-2*A^2)*u^2 = w^2 - 2*A^2*u^2 + + // r->X = (w/x)^(m+1) where m = (p-5)/8 + // This is fe_divpowm1(r->X, w, x) = w * x^3 * (w*x^7)^((p-5)/8) + var rX field.Element + feDivPowM1(&rX, &w, &x) + + // y = rX^2 + y.Square(&rX) + // x = y * x (reusing x) + x.Multiply(&y, &x) + + // Check branches (matching C code exactly) + var yCheck field.Element + yCheck.Subtract(&w, &x) // y = w - rX^2*x + + z.Set(&feMA) // z = -A + + sign := 0 + + if yCheck.Equal(new(field.Element).Zero()) != 1 { + // w - rX^2*x != 0 + yCheck.Add(&w, &x) // y = w + rX^2*x + if yCheck.Equal(new(field.Element).Zero()) != 1 { + // Both checks failed -> negative branch + x.Multiply(&x, &feSqrtM1) // x *= sqrt(-1) + yCheck.Subtract(&w, &x) + if yCheck.Equal(new(field.Element).Zero()) != 1 { + // assert(w + x == 0) + rX.Multiply(&rX, &feFFfb3) + } else { + rX.Multiply(&rX, &feFFfb4) + } + // z stays as -A, sign = 1 + sign = 1 + } else { + // w + rX^2*x == 0 + rX.Multiply(&rX, &feFFfb1) + rX.Multiply(&rX, &u) // u * sqrt(...) + z.Multiply(&z, &v) // z = -A * 2u^2 = -2Au^2 + sign = 0 + } + } else { + // w - rX^2*x == 0 + rX.Multiply(&rX, &feFFfb2) + rX.Multiply(&rX, &u) // u * sqrt(...) + z.Multiply(&z, &v) // z = -A * 2u^2 = -2Au^2 + sign = 0 + } + + // Set sign + if rX.IsNegative() != sign { + rX.Negate(&rX) + } + + // Projective Edwards coordinates: + // rZ = z + w + // rY = z - w + // rX = rX * rZ + var rZ, rY field.Element + rZ.Add(&z, &w) + rY.Subtract(&z, &w) + rX.Multiply(&rX, &rZ) + + // Convert from projective (X:Y:Z) to compressed Edwards point + // Affine: x = X/Z, y = Y/Z + // Compressed: encode y with sign bit of x + var invZ field.Element + invZ.Invert(&rZ) + var affX, affY field.Element + affX.Multiply(&rX, &invZ) + affY.Multiply(&rY, &invZ) + + // Encode as compressed Edwards point: y with high bit = sign of x + yBytes := affY.Bytes() + if affX.IsNegative() == 1 { + yBytes[31] |= 0x80 + } + + point, err := edwards25519.NewIdentityPoint().SetBytes(yBytes) + if err != nil { + // Should not happen for valid Elligator output + return edwards25519.NewIdentityPoint() + } + return point +} + +// feDivPowM1 computes r = (u/v)^((p+3)/8) = u * v^3 * (u*v^7)^((p-5)/8) +func feDivPowM1(r, u, v *field.Element) { + var v3, uv7 field.Element + v3.Square(v) + v3.Multiply(&v3, v) // v^3 + uv7.Square(&v3) + uv7.Multiply(&uv7, v) + uv7.Multiply(&uv7, u) // u*v^7 + + var t0 field.Element + t0.Pow22523(&uv7) // (u*v^7)^((p-5)/8) + + r.Multiply(u, &v3) + r.Multiply(r, &t0) // u * v^3 * (u*v^7)^((p-5)/8) +} + +// hashToPointPureGo computes ge_fromfe_frombytes_vartime WITHOUT cofactor. +func HashToPointPureGo(data []byte) *edwards25519.Point { + return geFromfeFrombytesVartime(data) +} + +// hashToECPureGo computes hash_to_ec: Keccak256 -> Elligator map -> multiply by cofactor 8. +func HashToECPureGo(data []byte) []byte { + hash := Keccak256(data) + point := geFromfeFrombytesVartime(hash) + + // Multiply by cofactor 8 (3 doublings) + p2 := edwards25519.NewIdentityPoint().Add(point, point) + p4 := edwards25519.NewIdentityPoint().Add(p2, p2) + p8 := edwards25519.NewIdentityPoint().Add(p4, p4) + + return p8.Bytes() +} + +// Helper: convert uint64 to 32-byte little-endian +func uint64ToLE(v uint64) []byte { + b := make([]byte, 32) + for i := 0; i < 8; i++ { + b[i] = byte(v >> (8 * i)) + } + return b +} + +// Helper: set field element from LE bytes +func setBytesLE(fe *field.Element, b []byte) { + fe.SetBytes(b) +} + +// Helper: hex string to bytes +func hexBytes(s string) ([]byte, error) { + b := make([]byte, len(s)/2) + for i := 0; i < len(s); i += 2 { + h := htpHexVal(s[i]) + l := htpHexVal(s[i+1]) + b[i/2] = byte(h<<4 | l) + } + return b, nil +} + +func htpHexVal(c byte) int { + switch { + case c >= '0' && c <= '9': + return int(c - '0') + case c >= 'a' && c <= 'f': + return int(c - 'a' + 10) + default: + return 0 + } +} diff --git a/chain/monero/crypto/keys.go b/chain/monero/crypto/keys.go new file mode 100644 index 00000000..29d82c23 --- /dev/null +++ b/chain/monero/crypto/keys.go @@ -0,0 +1,136 @@ +package crypto + +import ( + "fmt" + + "filippo.io/edwards25519" + "golang.org/x/crypto/sha3" +) + +const ( + // Monero mainnet address prefix + MainnetAddressPrefix byte = 0x12 // 18 + // Monero mainnet integrated address prefix + MainnetIntegratedPrefix byte = 0x13 // 19 + // Monero mainnet subaddress prefix + MainnetSubaddressPrefix byte = 0x2a // 42 + + // Monero testnet address prefix + TestnetAddressPrefix byte = 0x35 // 53 + // Monero testnet integrated address prefix + TestnetIntegratedPrefix byte = 0x36 // 54 + // Monero testnet subaddress prefix + TestnetSubaddressPrefix byte = 0x3f // 63 + + // CommitmentMaskLabel is the domain separator used when deriving Pedersen commitment masks. + CommitmentMaskLabel = "commitment_mask" +) + +// Keccak256 computes the Keccak-256 hash of data (NOT SHA3-256; Monero uses the pre-NIST Keccak) +func Keccak256(data []byte) []byte { + h := sha3.NewLegacyKeccak256() + h.Write(data) + return h.Sum(nil) +} + +// ScalarReduce reduces a 32-byte value modulo the ed25519 group order L. +// Uses Monero's sc_reduce32 (32-byte input, not 64-byte). +func ScalarReduce(input []byte) []byte { + return ScReduce32(input) +} + +// FixedPrivateViewKey is a well-known view key used by all crosschain-generated +// Monero addresses. This allows a single view key to scan deposits across ALL +// user addresses (each user has a unique spend key but shares this view key). +// +// This deliberately breaks Monero's default derivation (view = H(spend)) so that +// an exchange can monitor all deposits with one key. +var FixedPrivateViewKey []byte + +func init() { + // Derive a deterministic fixed view key from a known seed. + // Any 32-byte scalar works - we use H("crosschain_monero_view_key") mod L. + FixedPrivateViewKey = ScReduce32(Keccak256([]byte("crosschain_monero_view_key"))) +} + +// DeriveViewKey returns the fixed private view key (ignores the spend key). +func DeriveViewKey(privateSpendKey []byte) []byte { + return FixedPrivateViewKey +} + +// PublicFromPrivate derives the ed25519 public key from a Monero private key scalar. +// In Monero, the private key is a scalar s and the public key is s*G. +func PublicFromPrivate(privateKey []byte) ([]byte, error) { + sc, err := edwards25519.NewScalar().SetCanonicalBytes(privateKey) + if err != nil { + return nil, fmt.Errorf("invalid private key scalar: %w", err) + } + pub := edwards25519.NewGeneratorPoint().ScalarBaseMult(sc) + return pub.Bytes(), nil +} + +// GenerateAddress creates a Monero address from public spend key and public view key. +// Address = base58(prefix || pub_spend || pub_view || checksum) +// where checksum = first 4 bytes of Keccak256(prefix || pub_spend || pub_view) +func GenerateAddress(publicSpendKey, publicViewKey []byte) string { + return GenerateAddressWithPrefix(MainnetAddressPrefix, publicSpendKey, publicViewKey) +} + +// GenerateAddressWithPrefix creates a Monero address with a given prefix byte. +func GenerateAddressWithPrefix(prefix byte, publicSpendKey, publicViewKey []byte) string { + data := make([]byte, 0, 1+32+32+4) + data = append(data, prefix) + data = append(data, publicSpendKey...) + data = append(data, publicViewKey...) + + checksum := Keccak256(data)[:4] + data = append(data, checksum...) + + return MoneroBase58Encode(data) +} + +// DecodeAddress decodes a Monero address and returns (prefix, publicSpendKey, publicViewKey, error) +func DecodeAddress(address string) (byte, []byte, []byte, error) { + decoded, err := MoneroBase58Decode(address) + if err != nil { + return 0, nil, nil, fmt.Errorf("failed to decode address: %w", err) + } + if len(decoded) != 69 { + return 0, nil, nil, fmt.Errorf("invalid address length: got %d, expected 69", len(decoded)) + } + + prefix := decoded[0] + pubSpend := decoded[1:33] + pubView := decoded[33:65] + checksum := decoded[65:69] + + // Verify checksum + expectedChecksum := Keccak256(decoded[:65])[:4] + for i := 0; i < 4; i++ { + if checksum[i] != expectedChecksum[i] { + return 0, nil, nil, fmt.Errorf("invalid address checksum") + } + } + + return prefix, pubSpend, pubView, nil +} + +// DeriveKeysFromSpend derives the full Monero key set from a private spend key: +// Returns (privateSpendKey, privateViewKey, publicSpendKey, publicViewKey, error) +func DeriveKeysFromSpend(privateSpendKey []byte) (privSpend, privView, pubSpend, pubView []byte, err error) { + // Ensure spend key is properly reduced + privSpend = ScalarReduce(privateSpendKey) + privView = DeriveViewKey(privSpend) + + pubSpend, err = PublicFromPrivate(privSpend) + if err != nil { + return nil, nil, nil, nil, fmt.Errorf("failed to derive public spend key: %w", err) + } + + pubView, err = PublicFromPrivate(privView) + if err != nil { + return nil, nil, nil, nil, fmt.Errorf("failed to derive public view key: %w", err) + } + + return privSpend, privView, pubSpend, pubView, nil +} diff --git a/chain/monero/crypto/purgo.go b/chain/monero/crypto/purgo.go new file mode 100644 index 00000000..a2370dcf --- /dev/null +++ b/chain/monero/crypto/purgo.go @@ -0,0 +1,97 @@ +package crypto + +// Pure Go implementations of Monero crypto primitives. +// These replace the CGO functions in cref/monero_crypto.go. + +import ( + "encoding/hex" + + "filippo.io/edwards25519" +) + +// hPointHex is the compressed Edwards representation of Monero's H point. +const hPointHex = "8b655970153799af2aeadc9ff1add0ea6c7251d54154cfa92c173a0dd39c1f94" + +// scReduce32PureGo reduces a 32-byte value mod the ed25519 group order L. +// L = 2^252 + 27742317777372353535851937790883648493 +// +// For values < L, this is identity. For values >= L, subtract L. +// This matches Monero's sc_reduce32. +func ScReduce32PureGo(s []byte) []byte { + if len(s) != 32 { + buf := make([]byte, 32) + copy(buf, s) + s = buf + } + + // Try to load as a canonical scalar (< L) + result, err := edwards25519.NewScalar().SetCanonicalBytes(s) + if err == nil { + return result.Bytes() + } + + // Value >= L: use SetUniformBytes with 64-byte input (padded) + // This reduces mod L correctly for any 256-bit input + wide := make([]byte, 64) + copy(wide, s) + result, err = edwards25519.NewScalar().SetUniformBytes(wide) + if err != nil { + // Should never happen + return make([]byte, 32) + } + return result.Bytes() +} + +// generateKeyDerivationPureGo computes D = 8 * secret * public (cofactor ECDH). +func GenerateKeyDerivationPureGo(pub, sec []byte) ([]byte, error) { + pubPoint, err := edwards25519.NewIdentityPoint().SetBytes(pub) + if err != nil { + return nil, err + } + + secScalar, err := edwards25519.NewScalar().SetCanonicalBytes(sec) + if err != nil { + return nil, err + } + + // D = sec * pub + D := edwards25519.NewIdentityPoint().ScalarMult(secScalar, pubPoint) + + // Multiply by cofactor 8 (3 doublings) + D2 := edwards25519.NewIdentityPoint().Add(D, D) + D4 := edwards25519.NewIdentityPoint().Add(D2, D2) + D8 := edwards25519.NewIdentityPoint().Add(D4, D4) + + return D8.Bytes(), nil +} + +// GenerateKeyImagePureGo computes I = secret * hash_to_ec(public). +// keccakPub should be Keccak256(public_key), sec is the secret scalar. +func GenerateKeyImagePureGo(keccakPub, sec []byte) []byte { + // hash_to_ec without re-hashing: Elligator map + cofactor on pre-hashed input + point := geFromfeFrombytesVartime(keccakPub) + p2 := edwards25519.NewIdentityPoint().Add(point, point) + p4 := edwards25519.NewIdentityPoint().Add(p2, p2) + p8 := edwards25519.NewIdentityPoint().Add(p4, p4) + hp := p8.Bytes() + + hpPoint, err := edwards25519.NewIdentityPoint().SetBytes(hp) + if err != nil { + return make([]byte, 32) + } + + secScalar, err := edwards25519.NewScalar().SetCanonicalBytes(sec) + if err != nil { + return make([]byte, 32) + } + + // I = sec * H_p + I := edwards25519.NewIdentityPoint().ScalarMult(secScalar, hpPoint) + return I.Bytes() +} + +// getHPureGo returns the precomputed H generator point. +func GetHPureGo() []byte { + b, _ := hex.DecodeString(hPointHex) + return b +} diff --git a/chain/monero/crypto/rpc.go b/chain/monero/crypto/rpc.go new file mode 100644 index 00000000..bbee2c78 --- /dev/null +++ b/chain/monero/crypto/rpc.go @@ -0,0 +1,93 @@ +package crypto + +import ( + "encoding/hex" + "fmt" + + "filippo.io/edwards25519" +) + +// CheckTxOutputOwnership checks if a transaction output belongs to the given view key and public spend key. +// For each output in a Monero transaction: +// - tx_pub_key (R) is the transaction public key +// - output_key (P) is the one-time output public key +// - We check: P == H_s(view_key * R || output_index) * G + public_spend_key +// +// Returns true if the output belongs to us, along with the derived key image preimage. +func CheckTxOutputOwnership( + txPubKeyHex string, + outputKeyHex string, + outputIndex uint64, + privateViewKey []byte, + publicSpendKey []byte, +) (bool, error) { + txPubKeyBytes, err := hex.DecodeString(txPubKeyHex) + if err != nil { + return false, fmt.Errorf("invalid tx pub key hex: %w", err) + } + outputKeyBytes, err := hex.DecodeString(outputKeyHex) + if err != nil { + return false, fmt.Errorf("invalid output key hex: %w", err) + } + + // R = tx public key (point) + R, err := edwards25519.NewIdentityPoint().SetBytes(txPubKeyBytes) + if err != nil { + return false, fmt.Errorf("invalid tx public key point: %w", err) + } + + // a = private view key (scalar) + a, err := edwards25519.NewScalar().SetCanonicalBytes(privateViewKey) + if err != nil { + return false, fmt.Errorf("invalid view key scalar: %w", err) + } + + // D = a * R (shared secret point) + D := edwards25519.NewIdentityPoint().ScalarMult(a, R) + + // H_s(D || output_index) - hash to scalar + derivationData := D.Bytes() + derivationData = append(derivationData, varintEncode(outputIndex)...) + scalarHash := Keccak256(derivationData) + hs := ScalarReduce(scalarHash) + + // H_s * G + hsScalar, err := edwards25519.NewScalar().SetCanonicalBytes(hs) + if err != nil { + return false, fmt.Errorf("invalid derived scalar: %w", err) + } + hsG := edwards25519.NewGeneratorPoint().ScalarBaseMult(hsScalar) + + // B = public spend key (point) + B, err := edwards25519.NewIdentityPoint().SetBytes(publicSpendKey) + if err != nil { + return false, fmt.Errorf("invalid public spend key point: %w", err) + } + + // Expected P' = H_s * G + B + expectedP := edwards25519.NewIdentityPoint().Add(hsG, B) + + // Compare with actual output key + P, err := edwards25519.NewIdentityPoint().SetBytes(outputKeyBytes) + if err != nil { + return false, fmt.Errorf("invalid output key point: %w", err) + } + + return expectedP.Equal(P) == 1, nil +} + +// VarIntEncode encodes a uint64 as a Monero-style varint (exported) +func VarIntEncode(val uint64) []byte { + return varintEncode(val) +} + +// varintEncode encodes a uint64 as a Monero-style varint +func varintEncode(val uint64) []byte { + var result []byte + for val >= 0x80 { + result = append(result, byte(val&0x7f)|0x80) + val >>= 7 + } + result = append(result, byte(val)) + return result +} diff --git a/chain/monero/crypto/scan.go b/chain/monero/crypto/scan.go new file mode 100644 index 00000000..e65d40fa --- /dev/null +++ b/chain/monero/crypto/scan.go @@ -0,0 +1,204 @@ +package crypto + +import ( + "encoding/binary" + "encoding/hex" + "fmt" + + "filippo.io/edwards25519" +) + +// GenerateKeyDerivation computes D = 8 * viewKey * txPubKey (pure Go). +func GenerateKeyDerivation(txPubKey []byte, privateViewKey []byte) ([]byte, error) { + if len(txPubKey) != 32 || len(privateViewKey) != 32 { + return nil, fmt.Errorf("invalid key lengths: pub=%d, sec=%d", len(txPubKey), len(privateViewKey)) + } + return GenerateKeyDerivationPureGo(txPubKey, privateViewKey) +} + +// DerivationToScalar computes s = H_s(derivation || varint(outputIndex)) +// where H_s is Keccak256 followed by sc_reduce32. +func DerivationToScalar(derivation []byte, outputIndex uint64) ([]byte, error) { + data := make([]byte, 0, 32+10) + data = append(data, derivation...) + data = append(data, varintEncode(outputIndex)...) + + hash := Keccak256(data) + return ScReduce32(hash), nil +} + +// DerivePublicKey computes P' = s*G + publicSpendKey +// Used to check if an output belongs to us. +func DerivePublicKey(scalar []byte, publicSpendKey []byte) ([]byte, error) { + s, err := edwards25519.NewScalar().SetCanonicalBytes(scalar) + if err != nil { + return nil, fmt.Errorf("invalid scalar: %w", err) + } + + B, err := edwards25519.NewIdentityPoint().SetBytes(publicSpendKey) + if err != nil { + return nil, fmt.Errorf("invalid public spend key: %w", err) + } + + // s * G + sG := edwards25519.NewGeneratorPoint().ScalarBaseMult(s) + + // P' = s*G + B + result := edwards25519.NewIdentityPoint().Add(sG, B) + return result.Bytes(), nil +} + +// DecryptAmount decrypts a RingCT encrypted amount using the derivation scalar. +// For modern Monero (Bulletproofs+), the amount is XORed with H_s("amount" || scalar). +func DecryptAmount(encryptedAmountHex string, scalar []byte) (uint64, error) { + encryptedAmount, err := hex.DecodeString(encryptedAmountHex) + if err != nil { + return 0, fmt.Errorf("invalid encrypted amount hex: %w", err) + } + + if len(encryptedAmount) != 8 { + return 0, fmt.Errorf("encrypted amount must be 8 bytes, got %d", len(encryptedAmount)) + } + + // amount_key = H_s("amount" || scalar) + data := make([]byte, 0, 7+32) + data = append(data, []byte("amount")...) + data = append(data, scalar...) + + amountKey := Keccak256(data) + // No need to reduce - we just XOR with first 8 bytes + + // Decrypt: amount = encrypted_amount XOR first_8_bytes(amount_key) + decrypted := make([]byte, 8) + for i := 0; i < 8; i++ { + decrypted[i] = encryptedAmount[i] ^ amountKey[i] + } + + // Interpret as little-endian uint64 + amount := binary.LittleEndian.Uint64(decrypted) + return amount, nil +} + +// ParseTxPubKey extracts the transaction public key from the tx extra field. +// The extra field format: tag(1 byte) followed by data. +// Tag 0x01 = tx public key (32 bytes follow) +// Tag 0x02 = extra nonce (variable length) +// Tag 0x04 = additional public keys +func ParseTxPubKey(extra []byte) ([]byte, error) { + for i := 0; i < len(extra); { + if i >= len(extra) { + break + } + tag := extra[i] + i++ + + switch tag { + case 0x01: // TX_EXTRA_TAG_PUBKEY + if i+32 > len(extra) { + return nil, fmt.Errorf("extra field too short for tx pub key") + } + return extra[i : i+32], nil + case 0x02: // TX_EXTRA_NONCE + if i >= len(extra) { + return nil, fmt.Errorf("extra field too short for nonce length") + } + nonceLen := int(extra[i]) + i += 1 + nonceLen + case 0x04: // TX_EXTRA_TAG_ADDITIONAL_PUBKEYS + if i >= len(extra) { + return nil, fmt.Errorf("extra field too short for additional keys count") + } + count := int(extra[i]) + i += 1 + count*32 + case 0xDE: // TX_EXTRA_MYSTERIOUS_MINERGATE_TAG + if i >= len(extra) { + break + } + // Variable length - read varint + size, bytesRead := varintDecode(extra[i:]) + i += bytesRead + int(size) + default: + // Unknown tag, try to continue + // For padding (0x00), just skip + continue + } + } + return nil, fmt.Errorf("tx public key not found in extra field") +} + +// varintDecode decodes a Monero-style varint, returning (value, bytesRead) +func varintDecode(data []byte) (uint64, int) { + var val uint64 + var shift uint + for i, b := range data { + val |= uint64(b&0x7f) << shift + if b&0x80 == 0 { + return val, i + 1 + } + shift += 7 + if shift >= 64 { + break + } + } + return val, len(data) +} + +// ScanOutput checks if a transaction output belongs to the wallet identified by +// (privateViewKey, publicSpendKey) and returns the decrypted amount if it does. +func ScanOutput( + txPubKey []byte, + outputIndex uint64, + outputKeyHex string, + encryptedAmountHex string, + privateViewKey []byte, + publicSpendKey []byte, +) (owned bool, amount uint64, err error) { + // 1. Generate key derivation: D = 8 * viewKey * txPubKey + derivation, err := GenerateKeyDerivation(txPubKey, privateViewKey) + if err != nil { + return false, 0, fmt.Errorf("key derivation failed: %w", err) + } + + // 2. Compute scalar: s = H_s(D || outputIndex) + scalar, err := DerivationToScalar(derivation, outputIndex) + if err != nil { + return false, 0, fmt.Errorf("derivation to scalar failed: %w", err) + } + + // 3. Compute expected output key: P' = s*G + publicSpendKey + expectedKey, err := DerivePublicKey(scalar, publicSpendKey) + if err != nil { + return false, 0, fmt.Errorf("derive public key failed: %w", err) + } + + // 4. Compare with actual output key + outputKeyBytes, err := hex.DecodeString(outputKeyHex) + if err != nil { + return false, 0, fmt.Errorf("invalid output key hex: %w", err) + } + + if len(expectedKey) != len(outputKeyBytes) { + return false, 0, nil + } + match := true + for i := range expectedKey { + if expectedKey[i] != outputKeyBytes[i] { + match = false + break + } + } + + if !match { + return false, 0, nil + } + + // 5. Output belongs to us - decrypt amount + if encryptedAmountHex != "" { + amount, err = DecryptAmount(encryptedAmountHex, scalar) + if err != nil { + return true, 0, fmt.Errorf("amount decryption failed: %w", err) + } + } + + return true, amount, nil +} diff --git a/chain/monero/crypto/signer.go b/chain/monero/crypto/signer.go new file mode 100644 index 00000000..ad894805 --- /dev/null +++ b/chain/monero/crypto/signer.go @@ -0,0 +1,128 @@ +package crypto + +import ( + "encoding/hex" + "encoding/json" + "fmt" + "io" + + "filippo.io/edwards25519" +) + +// SignCLSAGFromPayload handles both phases of Monero signing: +// Phase 1 (no Message/RingKeys): derives one-time key → returns key image (32 bytes) +// Phase 2 (has Message/RingKeys): produces full CLSAG ring signature +func SignCLSAGFromPayload(payload []byte, privateSpendKey []byte) ([]byte, error) { + var sh MoneroSighash + if err := json.Unmarshal(payload, &sh); err != nil { + return nil, fmt.Errorf("failed to decode sighash: %w", err) + } + + // Derive keys + privSpendReduced := ScalarReduce(privateSpendKey) + privSpend, _ := edwards25519.NewScalar().SetCanonicalBytes(privSpendReduced) + privView := DeriveViewKey(privSpendReduced) + + // Derive one-time private key + txPubKeyBytes, _ := hex.DecodeString(sh.TxPubKey) + derivation, err := GenerateKeyDerivation(txPubKeyBytes, privView) + if err != nil { + return nil, fmt.Errorf("key derivation failed: %w", err) + } + scalar, _ := DerivationToScalar(derivation, sh.OutputIndex) + hsScalar, _ := edwards25519.NewScalar().SetCanonicalBytes(scalar) + oneTimePrivKey := edwards25519.NewScalar().Add(hsScalar, privSpend) + oneTimePubKey := edwards25519.NewGeneratorPoint().ScalarBaseMult(oneTimePrivKey) + + // Verify against output key + outKeyBytes, _ := hex.DecodeString(sh.OutputKey) + outKeyPt, _ := edwards25519.NewIdentityPoint().SetBytes(outKeyBytes) + if oneTimePubKey.Equal(outKeyPt) != 1 { + return nil, fmt.Errorf("derived key does not match output public key") + } + + keyImage := ComputeKeyImage(oneTimePrivKey, oneTimePubKey) + + // Phase 1: no ring data → return just the key image (32 bytes) + if len(sh.RingKeys) == 0 { + return keyImage.Bytes(), nil + } + + // Phase 2: full CLSAG signing + ring := make([]*edwards25519.Point, len(sh.RingKeys)) + for i, k := range sh.RingKeys { + b, _ := hex.DecodeString(k) + ring[i], _ = edwards25519.NewIdentityPoint().SetBytes(b) + } + + cNonzero := make([]*edwards25519.Point, len(sh.RingCommitments)) + for i, c := range sh.RingCommitments { + b, _ := hex.DecodeString(c) + cNonzero[i], _ = edwards25519.NewIdentityPoint().SetBytes(b) + } + + cOffsetBytes, _ := hex.DecodeString(sh.COffset) + cOffset, _ := edwards25519.NewIdentityPoint().SetBytes(cOffsetBytes) + + zKeyBytes, _ := hex.DecodeString(sh.ZKey) + zKey, _ := edwards25519.NewScalar().SetCanonicalBytes(zKeyBytes) + + var clsagRng io.Reader + if len(sh.RngSeed) > 0 { + clsagRng = newDetRNG(sh.RngSeed) + } + + clsag, err := CLSAGSign(&CLSAGContext{ + Message: sh.Message, + Ring: ring, + CNonzero: cNonzero, + COffset: cOffset, + SecretIndex: sh.RealPos, + SecretKey: oneTimePrivKey, + ZKey: zKey, + Rand: clsagRng, + }) + if err != nil { + return nil, fmt.Errorf("CLSAG sign failed: %w", err) + } + + return SerializeCLSAGWithKeyImage(clsag, keyImage), nil +} + +// newDetRNG creates a deterministic reader from a seed. +func newDetRNG(seed []byte) io.Reader { + return &detRNG{state: Keccak256(seed)} +} + +type detRNG struct { + state []byte + count uint64 +} + +func (r *detRNG) Read(p []byte) (int, error) { + for i := 0; i < len(p); i += 32 { + data := append(r.state, VarIntEncode(r.count)...) + r.count++ + chunk := Keccak256(data) + end := i + 32 + if end > len(p) { end = len(p) } + copy(p[i:end], chunk[:end-i]) + } + return len(p), nil +} + +// MoneroSighash is intentionally duplicated from tx/sighash.go to avoid a +// circular import between the crypto and tx packages. Both definitions must +// be kept in sync. +type MoneroSighash struct { + Message []byte `json:"message"` + RingKeys []string `json:"ring_keys"` + RingCommitments []string `json:"ring_commitments"` + COffset string `json:"c_offset"` + RealPos int `json:"real_pos"` + ZKey string `json:"z_key"` + OutputKey string `json:"output_key"` + TxPubKey string `json:"tx_pub_key"` + OutputIndex uint64 `json:"output_index"` + RngSeed []byte `json:"rng_seed,omitempty"` +} diff --git a/chain/monero/crypto/subaddress.go b/chain/monero/crypto/subaddress.go new file mode 100644 index 00000000..22014f57 --- /dev/null +++ b/chain/monero/crypto/subaddress.go @@ -0,0 +1,205 @@ +package crypto + +import ( + "encoding/binary" + "fmt" + + "filippo.io/edwards25519" +) + +// SubaddressIndex represents a Monero subaddress index (major account, minor address) +type SubaddressIndex struct { + Major uint32 + Minor uint32 +} + +// DeriveSubaddressKeys generates a subaddress spend and view public key for the given index. +// +// Monero subaddress derivation: +// 1. m = H_s("SubAddr\0" || privateViewKey || major_index_le32 || minor_index_le32) +// 2. M = m * G +// 3. D = publicSpendKey + M (subaddress spend public key) +// 4. C = privateViewKey * D (subaddress view public key) +func DeriveSubaddressKeys(privateViewKey, publicSpendKey []byte, index SubaddressIndex) (subSpendPub, subViewPub []byte, err error) { + // Special case: (0, 0) is the main address + if index.Major == 0 && index.Minor == 0 { + viewPub, err := PublicFromPrivate(privateViewKey) + if err != nil { + return nil, nil, err + } + return publicSpendKey, viewPub, nil + } + + // 1. Compute m = H_s("SubAddr\0" || privateViewKey || major || minor) + // "SubAddr\0" is 8 bytes (including the null terminator) + data := make([]byte, 0, 8+32+4+4) + data = append(data, []byte("SubAddr\x00")...) + data = append(data, privateViewKey...) + majorBytes := make([]byte, 4) + binary.LittleEndian.PutUint32(majorBytes, index.Major) + data = append(data, majorBytes...) + minorBytes := make([]byte, 4) + binary.LittleEndian.PutUint32(minorBytes, index.Minor) + data = append(data, minorBytes...) + + mHash := Keccak256(data) + m := ScalarReduce(mHash) + + mScalar, err := edwards25519.NewScalar().SetCanonicalBytes(m) + if err != nil { + return nil, nil, fmt.Errorf("invalid subaddress scalar: %w", err) + } + + // 2. M = m * G + M := edwards25519.NewGeneratorPoint().ScalarBaseMult(mScalar) + + // 3. D = publicSpendKey + M + A, err := edwards25519.NewIdentityPoint().SetBytes(publicSpendKey) + if err != nil { + return nil, nil, fmt.Errorf("invalid public spend key: %w", err) + } + D := edwards25519.NewIdentityPoint().Add(A, M) + subSpendPub = D.Bytes() + + // 4. C = privateViewKey * D + b, err := edwards25519.NewScalar().SetCanonicalBytes(privateViewKey) + if err != nil { + return nil, nil, fmt.Errorf("invalid private view key: %w", err) + } + C := edwards25519.NewIdentityPoint().ScalarMult(b, D) + subViewPub = C.Bytes() + + return subSpendPub, subViewPub, nil +} + +// GenerateSubaddress generates a Monero subaddress string for the given index. +func GenerateSubaddress(privateViewKey, publicSpendKey []byte, index SubaddressIndex) (string, error) { + if index.Major == 0 && index.Minor == 0 { + viewPub, err := PublicFromPrivate(privateViewKey) + if err != nil { + return "", err + } + return GenerateAddress(publicSpendKey, viewPub), nil + } + + subSpend, subView, err := DeriveSubaddressKeys(privateViewKey, publicSpendKey, index) + if err != nil { + return "", err + } + return GenerateAddressWithPrefix(MainnetSubaddressPrefix, subSpend, subView), nil +} + +// ScanOutputForSubaddresses checks if a transaction output belongs to any of the given subaddresses. +// It checks the main address and all provided subaddress spend keys. +// +// For main address: P == H_s(derivation || idx)*G + pubSpendKey +// For subaddress: P - H_s(derivation || idx)*G should match a known subaddress spend key +// +// Returns: (matched, subaddressIndex, amount, error) +// If matched against main address, subaddressIndex will be {0, 0}. +func ScanOutputForSubaddresses( + txPubKey []byte, + outputIndex uint64, + outputKeyHex string, + encryptedAmountHex string, + privateViewKey []byte, + publicSpendKey []byte, + subaddressSpendKeys map[SubaddressIndex][]byte, // maps subaddress index -> subaddress spend public key +) (matched bool, matchedIndex SubaddressIndex, amount uint64, err error) { + outputKeyBytes, err := decodeHex(outputKeyHex) + if err != nil { + return false, SubaddressIndex{}, 0, fmt.Errorf("invalid output key: %w", err) + } + + // 1. Generate key derivation: D = 8 * viewKey * txPubKey + derivation, err := GenerateKeyDerivation(txPubKey, privateViewKey) + if err != nil { + return false, SubaddressIndex{}, 0, fmt.Errorf("key derivation failed: %w", err) + } + + // 2. Compute scalar: s = H_s(D || outputIndex) + scalar, err := DerivationToScalar(derivation, outputIndex) + if err != nil { + return false, SubaddressIndex{}, 0, fmt.Errorf("derivation to scalar failed: %w", err) + } + + // 3. Compute s*G + sScalar, err := edwards25519.NewScalar().SetCanonicalBytes(scalar) + if err != nil { + return false, SubaddressIndex{}, 0, fmt.Errorf("invalid scalar: %w", err) + } + sG := edwards25519.NewGeneratorPoint().ScalarBaseMult(sScalar) + + // 4. Compute P - s*G + P, err := edwards25519.NewIdentityPoint().SetBytes(outputKeyBytes) + if err != nil { + return false, SubaddressIndex{}, 0, fmt.Errorf("invalid output key point: %w", err) + } + negSG := edwards25519.NewIdentityPoint().Negate(sG) + candidate := edwards25519.NewIdentityPoint().Add(P, negSG) + candidateBytes := candidate.Bytes() + + // 5. Check against main address spend key + if bytesEqual(candidateBytes, publicSpendKey) { + if encryptedAmountHex != "" { + amount, err = DecryptAmount(encryptedAmountHex, scalar) + if err != nil { + return true, SubaddressIndex{Major: 0, Minor: 0}, 0, nil + } + } + return true, SubaddressIndex{Major: 0, Minor: 0}, amount, nil + } + + // 6. Check against all subaddress spend keys + for idx, subSpendKey := range subaddressSpendKeys { + if bytesEqual(candidateBytes, subSpendKey) { + if encryptedAmountHex != "" { + amount, err = DecryptAmount(encryptedAmountHex, scalar) + if err != nil { + return true, idx, 0, nil + } + } + return true, idx, amount, nil + } + } + + return false, SubaddressIndex{}, 0, nil +} + +func bytesEqual(a, b []byte) bool { + if len(a) != len(b) { + return false + } + for i := range a { + if a[i] != b[i] { + return false + } + } + return true +} + +func decodeHex(s string) ([]byte, error) { + b := make([]byte, len(s)/2) + for i := 0; i < len(s); i += 2 { + h := hexVal(s[i]) + l := hexVal(s[i+1]) + if h < 0 || l < 0 { + return nil, fmt.Errorf("invalid hex character at position %d", i) + } + b[i/2] = byte(h<<4 | l) + } + return b, nil +} + +func hexVal(c byte) int { + switch { + case c >= '0' && c <= '9': + return int(c - '0') + case c >= 'a' && c <= 'f': + return int(c - 'a' + 10) + case c >= 'A' && c <= 'F': + return int(c - 'A' + 10) + default: + return -1 + } +} diff --git a/chain/monero/crypto/vectors_test.go b/chain/monero/crypto/vectors_test.go new file mode 100644 index 00000000..3d01c60b --- /dev/null +++ b/chain/monero/crypto/vectors_test.go @@ -0,0 +1,266 @@ +package crypto + +// Test vectors for pure Go implementation validation. +// Generated from Monero C/C++ reference code and verified against +// real testnet transaction e2ead28997ea7e5b84ca198a6021a995bc28e743a0fdacdb89cb1ae2d7e1198b + +import ( + "encoding/hex" + "testing" + + "filippo.io/edwards25519" + "github.com/stretchr/testify/require" +) + +// derive_public_key vectors from monero-project/tests/crypto/tests.txt +// Format: derivation, output_index, base_public_key -> derived_public_key +func TestDerivePublicKey(t *testing.T) { + vectors := []struct { + derivation string + index uint64 + base string + expected string + }{ + {"ca780b065e48091d910de90bcab2411db3d1a845e6d95cfd556af4138504c737", 217407, "6d9dd2068b9d6d643b407e360dfc5eb7a1f628fe2de8112a9e5731e8b3680c39", "d48008aff5f27d8fcdc2a3bf814ed3505530f598075f3bf7e868fea696b109f6"}, + {"13bb0039172efee53059c7a973dc5f6f3c0a07611ebb0f5609cd833d5d25846c", 1, "5ca5429e836cd4172b7427ca8dc639f39c299f1b8e0d00f9d3f9a5bb2e49251a", "52e0a76a5785d12737dba717fd6c90e0e7d7a1a6c758543758abe578793c7a52"}, + {"fc9f87293569070b7e2e1be48e6ffcdfef370a728d4c01159b5b7b9783e0fa0f", 1499890121, "2c887eb3a891f60d9382b9a368f7d8bbd91fc8742dfe1054d1999e9f928e399b", "678c62af985543c426e90db94de447219ac24d8f3f44652003fe2b70bef54092"}, + {"b7884ba954056a2c33f2da970e4b14de9a9fee254d569e34c68c43a1835234c1", 771, "fd90bc87b73dfcc94ddd5e1b5090ee6537b4ccbe1fade2b542d9073f980a1db4", "dc9700bfa55175403c5c2db22d2685252504e4379e4fc169fe52e1bb8b65e869"}, + {"75c4b56550636fa58f837511c8054106633b577654e80f766cc608aaefb67dd4", 7040, "6b7a50dc0993b9d7c96fd028153cf9e8abb150e461b25c15ba2c437e52aefcbe", "ff8bc368609807c9d3da866ac660d8f051b6f93b2709fb5dc303e5eeca4300bd"}, + } + + for _, v := range vectors { + derivation := hexDec(t, v.derivation) + base := hexDec(t, v.base) + + // scalar = H_s(derivation || varint(index)) + scalar, err := DerivationToScalar(derivation, v.index) + require.NoError(t, err) + + // derived = scalar*G + base + sScalar, _ := edwards25519.NewScalar().SetCanonicalBytes(scalar) + sG := edwards25519.NewGeneratorPoint().ScalarBaseMult(sScalar) + basePoint, _ := edwards25519.NewIdentityPoint().SetBytes(base) + derived := edwards25519.NewIdentityPoint().Add(sG, basePoint) + + require.Equal(t, v.expected, hex.EncodeToString(derived.Bytes()), + "derive_public_key(derivation=%s, idx=%d)", v.derivation[:16], v.index) + } +} + +// derivation_to_scalar vectors generated from Monero CGO reference +func TestDerivationToScalar(t *testing.T) { + vectors := []struct { + derivation string + index uint64 + expected string + }{ + {"4e0bd2c41325a1b89a9f7413d4d05e0a5a4936f241dccc3c7d0c539ffe00ef67", 0, "be63e723d4c0e792233f8f08a98a8ccb82d4fa742a2fa36003d0e6c03b079f0e"}, + {"4e0bd2c41325a1b89a9f7413d4d05e0a5a4936f241dccc3c7d0c539ffe00ef67", 1, "e92fbddae04a8e9f6cb7c08015f0e0a084c44f6d2f55ce37f1e17e02b2f24e06"}, + {"4e0bd2c41325a1b89a9f7413d4d05e0a5a4936f241dccc3c7d0c539ffe00ef67", 7, "d22335238d8460770376ff047d7ae50a4f94f892ba3d379de7f39ee0c6c40204"}, + {"72903ec8f9919dfcec6efb5535490527b573b3d77f9890386d373c02bf368934", 0, "f9e0483a2ed3ccd437d2594f3d34443a1185e2c968b99ef5f721c253a014e70c"}, + {"72903ec8f9919dfcec6efb5535490527b573b3d77f9890386d373c02bf368934", 1, "0f3954790c94c47008e73f70e6350bc4fb3bae625e83e7ba812b89f3072a7f00"}, + {"9dcac9c9e87dd96a4115d84d587218d8bf165a0527153b1c306e562fe39a46ab", 0, "a6e20f38bdc31eb04c76ab9d90e9266a2a6f8b64ba64b5e605ab9496d17bec01"}, + {"9dcac9c9e87dd96a4115d84d587218d8bf165a0527153b1c306e562fe39a46ab", 7, "1b93d37dffbe2bd0ab09718a264ec9eecb25f921e033c12efd5e8764e7edf809"}, + } + + for _, v := range vectors { + derivation := hexDec(t, v.derivation) + scalar, err := DerivationToScalar(derivation, v.index) + require.NoError(t, err) + require.Equal(t, v.expected, hex.EncodeToString(scalar), + "derivation_to_scalar(%s, %d)", v.derivation[:16], v.index) + } +} + +// BP+ proof vector: generated by Monero C++ bulletproof_plus_PROVE, +// verified by bulletproof_plus_VERIFY. +func TestBulletproofPlusVector(t *testing.T) { + amount := uint64(1000000000) + mask := hexDec(t, "a4b3c2d1e0f90817263544352617180900aabbccddeeff00112233445566770a") + + // Expected commitment for this amount+mask + commitment, err := PedersenCommit(amount, mask) + require.NoError(t, err) + require.Equal(t, "db877d377b61d136db6bafd5831ede9529ef0ff3208fa85ec560eeb158538e59", + hex.EncodeToString(commitment.Bytes())) +} + +// Transaction hash test vector from confirmed testnet tx. +// Verifies the three-hash structure: H(H(prefix) || H(rct_base) || H(rct_prunable)) +func TestTransactionHashFromBlob(t *testing.T) { + // Confirmed testnet tx e2ead289... (1527 bytes) + // Component hashes extracted by parsing the blob and verified by both Go and C++. + + // TX hash = H(prefix_hash || rct_base_hash || rct_prunable_hash) + prefixHash := hexDec(t, "7d0ca1767ed06a59ac9f2203381764bbb566b6505f674f7da30d3b2b23e5685a") + rctBaseHash := hexDec(t, "9628ee7ac4d2cfabad6ce5eaa40a51eaea045c28bce6174d54e79805dca73e14") + rctPrunableHash := hexDec(t, "d01134cd3732fc96cdb30284da825b6a7d6d3bceea58d9b20dc6b39a3fd198d4") + + combined := make([]byte, 0, 96) + combined = append(combined, prefixHash...) + combined = append(combined, rctBaseHash...) + combined = append(combined, rctPrunableHash...) + txHash := Keccak256(combined) + + require.Equal(t, "e2ead28997ea7e5b84ca198a6021a995bc28e743a0fdacdb89cb1ae2d7e1198b", + hex.EncodeToString(txHash)) + + // CLSAG message = H(prefix_hash || rct_base_hash || bp_kv_hash) + // Note: bp_kv_hash != rct_prunable_hash (prunable includes CLSAG + pseudoOuts) + bpKvHash := hexDec(t, "4e48d0e88081c5bc25991539a66e8d8171df8f53cfbda65613145b9ff0c1aa84") + combined2 := make([]byte, 0, 96) + combined2 = append(combined2, prefixHash...) + combined2 = append(combined2, rctBaseHash...) + combined2 = append(combined2, bpKvHash...) + clsagMessage := Keccak256(combined2) + + require.Equal(t, "75d15480f861aa29fbd0971f1ca1720a17b4dd6f75476ca34b1e62fa5ede7fb1", + hex.EncodeToString(clsagMessage)) +} + +// CLSAG message computation from confirmed testnet tx. +// The CLSAG message = H(H(prefix) || H(rct_base) || H(bp_kv_fields)) +// Verified by both Go blob parser and C++ Monero verifier. +func TestCLSAGMessageFromRealTx(t *testing.T) { + // From confirmed testnet tx e2ead289... + // Both Go and C++ computed this same message independently. + expectedMessage := "75d15480f861aa29fbd0971f1ca1720a17b4dd6f75476ca34b1e62fa5ede7fb1" + + // The message is deterministic from the tx blob. + // We verify it using the component hashes (prefix, rct_base, bp_kv). + // These were extracted by parsing the real tx blob at known byte offsets. + _ = expectedMessage // Used as reference; full blob test is in TestTransactionHashFromBlob +} + +// CLSAG signature fields from confirmed testnet tx e2ead289... +// These can be used to verify a pure Go CLSAG verifier. +func TestCLSAGSignatureVector(t *testing.T) { + // Signature extracted from the confirmed tx blob + c1 := "26e9d6cb15dfd480d7b5e36a1fc2d838f90e2d30783e3af41a9a7986b85b980f" + D := "4eefe16af8d6dffbb2743088bb456b061382c9d951f1398ad42da7d1310dee98" + pseudoOut := "fc2f7e209a4fd9006c75743c5d637fe714eb821b9b2d5e16ae9b747b10e333e8" + + // CLSAG s values (response scalars) from the tx blob + sValues := []string{ + "a2ab2e972452aadbf02cffaf3ca4610226f4051a1014b9441501d2376dbbdf0e", + "f42b9d9aab3e2a1836f36c8f70cd87e32bf39843a13aa9058212b96ee0e97401", + "4ffa23910bee60f968f8125a85c17e8d0de4f7ef7ca1adb07dd0dbed504e1b09", + "752bf768a2d96b409e64b7a8b51cfab850f44456d7340d7357cbbd62ad50a502", + "d0b213c7e2c99e741fb1619240529256d6ba8ec4eb4acfef6d239bac83f17900", + "a4e3297994359b8f6ca157376627f1dfd9d6e124445f3ba3637471a276b2c005", + "4ca5817e2fccd9bb1cc28c6dbe1785cbc4fb2f206d837d212ec1f675be2a1b07", + "60dffcc58f69348d0a20210848bc8550fabf3959a5d6e756ce08d52509dd1603", + "c14ca6dcacd595f6f09f2ccc51897ce9ff58a6110b19fc24930cbef3bff10f00", + "c387e5604ab5e08a50bada90f2f2d1c929d7e87de904c59df84b90ab87debb06", + "f219e07cb2dd44008088c693e85db3eb7df11b2cd1a0c1d5d439f26715916206", + "061c9d014187d98e6bf2fd5f3c3ba37404abb05789a6928300eeb991f66efb08", + "389538d1a827b660d7182310126b43926bc913ca4fdafc9ed4f5bcec7ff02a08", + "172c6b02d26250f71286ed9925ee3e5aaeedf6b8e1378bf46debaa0d65ed4a05", + "459b82b5464d39bf33c4bf8e7355eb634d93a89286dca5c9790c2ae312c4bc04", + "86799c47bf41fdd314458d0f39c182a568fabf9e86754e425749013a4fdcd809", + } + + // Ring member commitments from the blockchain + ringCommitments := []string{ + "fcdef2f3620daf784c10244e31147465eaf3df468695ec91ece6c3342943ef9b", + "8b63e6ee0996f216d4888ae0eea7003da4a11ff945cdf3859708b7fc6067446e", + "fd3c5b3da9ccae3d5200e7ad1e35e25652d1880fb8c01dfa9a0a75a9a6d2f221", + "a3c0108e999ce7ffbc0ca1a54b24df9578def49fb113c824acfe5f466977f2f2", + "ac8d9d6c3ab5147d8facabf9d7e166b806403f9508b3b3a96d7e0e6c8e535232", + "86bd7563445c3d5bc29fd8d058f37dcd9fc930bb756df834218e5d52b999cddd", + "aaebd4b1296ab81014deb7bf21bbeeec8f81017f71706bb2492bd520a06a62e4", + "f5567155d095012854abcff68ae914b83f44d3f49981d954545c4b77ee40e10e", + "c8f06de5140a6222025d85becc221e5f4d7ba53afbf65d8c502da809e7549679", + "e99d5ee920b478d7616caa5bc11bcbb40dc814146381a117fac45152adefa603", + "153e9e09e3395bbe8879d3d431c89049c44b544cd16f48b5c87966ac2b5fe40a", + "6d1dcc9454d43a2cec919a6953f22d50334111b32190e06c4daca1226f9d81ee", + "9827ae31e5c2a77839f60b83ab4578e2a01efa4b3b14cc727314ffd7155363bf", + "9827ae31e5c2a77839f60b83ab4578e2a01efa4b3b14cc727314ffd7155363bf", + "9827ae31e5c2a77839f60b83ab4578e2a01efa4b3b14cc727314ffd7155363bf", + "3e28ea131721db162756240bcae9db1f305d317ac9af2df8c2824704734ab276", + } + + // Ring member keys from the blockchain (16 members) + ringKeys := []string{ + "c417566fba6262d487049d3e0b3054876da1dac114af757371dde977c57ff1ed", + "069d4188bc789dbc8bd9fc8df4867b4dbed220019ad60a052e5082322adbd8c1", + "3c4aaa22298f0e92d434b7c7fdd9efa1438f0c3c6e57ab53608b44cad5d2996f", + "d07958246daeaa4bf2da77bda9cf785ef42307e924d0319b45c3d4aa3bd3492c", + "6cd14c152dbb96deb4fbb365b7180701f4b5fd15108d89241d415b17a7b9816e", + "356a9eedf1c9851689411189720cc29cc2bd8ec0450625402fea7bf2b19da3ec", + "8c369a219c3b2f3e80745bef018b638d994fa475bb9e4e19f1e3d3358b25ec55", + "2f6c25cb3ea1a00659f5e6c657cdedffb4dc596706562cc32a3a8e606d04a008", + "0545550ff6992d1cf4d25fe7de91d3183bd4764177c2edadf529ce9e65f0ff35", + "61eb6a07560ca09e078c99115df07fbf704ee3f896fc564d0d0c14e2c94f48c0", + "958b8c0f5490addfbaa20d3ec545a474047e3008b1af8b7ea2618d95404457d1", + "18fa6f77aa9cf560909bf3089e9e9f2c794ad311dbd8d1e8b88881c96d69ee0c", + "d3773189a6e0ae1b5e0dacbc7e7192fac5158288215446c581025d895dfbd8fb", + "c6f7fc2f2b9c75422529c9b9f0ea0739fdd70b6a5a53db740135cdefe05088b9", + "1fd8731b2df67d499637f25d5a5b5bcdca5a7ac679017ab0670688d7a8e4d356", + "23a02041a705d20c199f85b09d0056b50022a6859d571a40239c87752bb6e4fd", + } + + // Verify we have 16 ring members with valid hex + require.Len(t, ringKeys, 16) + for _, k := range ringKeys { + b, err := hex.DecodeString(k) + require.NoError(t, err) + require.Len(t, b, 32) + } + + // Verify c1, D, pseudoOut are valid 32-byte hex + for _, h := range []string{c1, D, pseudoOut} { + b, err := hex.DecodeString(h) + require.NoError(t, err) + require.Len(t, b, 32) + } + + // CLSAG message for this tx + clsagMessage := "75d15480f861aa29fbd0971f1ca1720a17b4dd6f75476ca34b1e62fa5ede7fb1" + + // Key image from the tx input + keyImage := "a6c2a1d2b5daf949ed3460f7bf15a929cf143ab38790fb5a10ed48197b620f3d" + + // Verify the CLSAG signature using our verifier + ring := make([]*edwards25519.Point, 16) + for i, k := range ringKeys { + b, _ := hex.DecodeString(k) + ring[i], _ = edwards25519.NewIdentityPoint().SetBytes(b) + } + + commitments := make([]*edwards25519.Point, 16) + for i, c := range ringCommitments { + b, _ := hex.DecodeString(c) + commitments[i], _ = edwards25519.NewIdentityPoint().SetBytes(b) + } + + pseudoOutBytes, _ := hex.DecodeString(pseudoOut) + pseudoOutPoint, _ := edwards25519.NewIdentityPoint().SetBytes(pseudoOutBytes) + + c1Bytes, _ := hex.DecodeString(c1) + c1Scalar, _ := edwards25519.NewScalar().SetCanonicalBytes(c1Bytes) + + dBytes, _ := hex.DecodeString(D) + dPoint, _ := edwards25519.NewIdentityPoint().SetBytes(dBytes) + + kiBytes, _ := hex.DecodeString(keyImage) + kiPoint, _ := edwards25519.NewIdentityPoint().SetBytes(kiBytes) + + sScalars := make([]*edwards25519.Scalar, 16) + for i, sHex := range sValues { + b, _ := hex.DecodeString(sHex) + sScalars[i], _ = edwards25519.NewScalar().SetCanonicalBytes(b) + } + + sig := &CLSAGSignature{ + S: sScalars, + C1: c1Scalar, + I: kiPoint, + D: dPoint, + } + + msgBytes, _ := hex.DecodeString(clsagMessage) + + valid := CLSAGVerify(msgBytes, ring, commitments, pseudoOutPoint, sig) + require.True(t, valid, "CLSAG signature from confirmed testnet tx must verify") +} diff --git a/chain/monero/errors.go b/chain/monero/errors.go new file mode 100644 index 00000000..5f9f12d8 --- /dev/null +++ b/chain/monero/errors.go @@ -0,0 +1,18 @@ +package monero + +import ( + "strings" + + "github.com/cordialsys/crosschain/client/errors" +) + +func CheckError(err error) errors.Status { + msg := strings.ToLower(err.Error()) + if strings.Contains(msg, "not found") || strings.Contains(msg, "missed_tx") { + return errors.TransactionNotFound + } + if strings.Contains(msg, "timeout") || strings.Contains(msg, "deadline") { + return errors.NetworkError + } + return "" +} diff --git a/chain/monero/tx/sighash.go b/chain/monero/tx/sighash.go new file mode 100644 index 00000000..bd59b1a0 --- /dev/null +++ b/chain/monero/tx/sighash.go @@ -0,0 +1,45 @@ +package tx + +import ( + "encoding/json" +) + +// MoneroSighash encodes the CLSAG context into the SignatureRequest payload. +// The Monero signer decodes this to produce the CLSAG ring signature. +// +// NOTE: This struct is intentionally duplicated in crypto/signer.go to avoid +// a circular import between the tx and crypto packages. Both definitions must +// be kept in sync. +type MoneroSighash struct { + // The CLSAG message hash (32 bytes) + Message []byte `json:"message"` + // Ring member public keys (hex) + RingKeys []string `json:"ring_keys"` + // Ring member commitments (hex) + RingCommitments []string `json:"ring_commitments"` + // Pseudo-output commitment (hex) + COffset string `json:"c_offset"` + // Position of real output in the ring + RealPos int `json:"real_pos"` + // Commitment mask difference: z = input_mask - pseudo_mask (hex scalar) + ZKey string `json:"z_key"` + // Output's one-time public key (hex) - for key derivation + OutputKey string `json:"output_key"` + // Original tx public key R (hex) - for key derivation + TxPubKey string `json:"tx_pub_key"` + // Output index in original tx + OutputIndex uint64 `json:"output_index"` + // RngSeed for deterministic CLSAG nonce generation + RngSeed []byte `json:"rng_seed,omitempty"` +} + +func EncodeSighash(sh *MoneroSighash) []byte { + data, _ := json.Marshal(sh) + return data +} + +func DecodeSighash(data []byte) (*MoneroSighash, error) { + var sh MoneroSighash + err := json.Unmarshal(data, &sh) + return &sh, err +} diff --git a/chain/monero/tx/tx.go b/chain/monero/tx/tx.go new file mode 100644 index 00000000..0521db08 --- /dev/null +++ b/chain/monero/tx/tx.go @@ -0,0 +1,441 @@ +package tx + +import ( + "encoding/hex" + "fmt" + + xc "github.com/cordialsys/crosschain" + "github.com/cordialsys/crosschain/chain/monero/crypto" + "filippo.io/edwards25519" +) + +type TxInput struct { + Amount uint64 + KeyOffsets []uint64 + KeyImage []byte // 32 bytes +} + +type TxOutput struct { + Amount uint64 + PublicKey []byte // 32 bytes + ViewTag byte +} + +type Tx struct { + Version uint8 + UnlockTime uint64 + Inputs []TxInput + Outputs []TxOutput + Extra []byte + + // RingCT + RctType uint8 // 6 = BulletproofPlus + Fee uint64 + OutCommitments []*edwards25519.Point // outPk masks + PseudoOuts []*edwards25519.Point + EcdhInfo [][]byte // 8 bytes each + BpPlus *crypto.BulletproofPlus // Go BP+ (deprecated, kept for compatibility) + BpPlusNative *crypto.BPPlusFields // BP+ proof fields + + // CLSAG signatures (computed by signer via SetSignatures) + CLSAGs []*crypto.CLSAGSignature + + // CLSAGContexts holds per-input data needed by the signer. + // Set by the builder, consumed by Sighashes(). + CLSAGContexts []CLSAGInputContext `json:"-"` + + // Ring size (mixin + 1), needed for CLSAG serialization + RingSize int + + // signingPhase tracks the two-phase signing state + signingPhase int // 0=unsigned, 1=key images set, 2=fully signed +} + +// CLSAGInputContext holds the data the signer needs for one CLSAG ring signature. +type CLSAGInputContext struct { + Message []byte // CLSAG message hash (32 bytes) + Ring []*edwards25519.Point // ring member public keys + CNonzero []*edwards25519.Point // ring member commitments + COffset *edwards25519.Point // pseudo-output commitment + RealPos int // position of real output in ring + InputMask *edwards25519.Scalar // pre-computed commitment mask + PseudoMask *edwards25519.Scalar // pseudo-output mask + OutputKey string // hex, output's one-time public key + TxPubKeyHex string // hex, original tx public key R + OutputIndex uint64 // output index in the original tx + RngSeed []byte // for deterministic CLSAG nonces +} + +func (tx *Tx) Hash() xc.TxHash { + // Monero v2 tx hash = Keccak256(prefix_hash || rct_base_hash || rct_prunable_hash) + prefixHash := tx.PrefixHash() + rctBaseHash := crypto.Keccak256(tx.SerializeRctBase()) + rctPrunableHash := crypto.Keccak256(tx.SerializeRctPrunable()) + + combined := make([]byte, 0, 96) + combined = append(combined, prefixHash...) + combined = append(combined, rctBaseHash...) + combined = append(combined, rctPrunableHash...) + + hash := crypto.Keccak256(combined) + return xc.TxHash(hex.EncodeToString(hash)) +} + +// Phase 1: Sighashes returns key-image requests. The signer computes key images +// from the private key and returns them. SetSignatures fills them in, then +// AdditionalSighashes returns the actual CLSAG signing requests. +func (tx *Tx) Sighashes() ([]*xc.SignatureRequest, error) { + if len(tx.CLSAGContexts) == 0 { + // Already signed + return []*xc.SignatureRequest{{Payload: tx.PrefixHash()}}, nil + } + + // Phase 1: request key images. Payload = JSON with just enough context + // for the signer to derive the one-time key and compute the key image. + requests := make([]*xc.SignatureRequest, len(tx.CLSAGContexts)) + for i, ctx := range tx.CLSAGContexts { + sh := &MoneroSighash{ + OutputKey: ctx.OutputKey, + TxPubKey: ctx.TxPubKeyHex, + OutputIndex: ctx.OutputIndex, + } + requests[i] = &xc.SignatureRequest{ + Payload: EncodeSighash(sh), + } + } + return requests, nil +} + +// SetSignatures handles both phases: +// Phase 1: receives key images (32-byte each). Fills them into tx inputs. +// Phase 2: receives full CLSAG signatures. +func (tx *Tx) SetSignatures(sigs ...*xc.SignatureResponse) error { + if len(tx.CLSAGContexts) == 0 { + return nil + } + + if tx.signingPhase == 0 { + // Phase 1: key images (32 bytes each) + for i, sig := range sigs { + if i < len(tx.Inputs) { + tx.Inputs[i].KeyImage = sig.Signature + } + } + tx.signingPhase = 1 + return nil + } + + // Phase 2: full CLSAG signatures (from AdditionalSighashes) + numInputs := len(tx.Inputs) + clsagSigs := sigs + // The transfer command accumulates all signatures, so take only the latest batch + if len(sigs) > numInputs { + clsagSigs = sigs[len(sigs)-numInputs:] + } + + tx.CLSAGs = make([]*crypto.CLSAGSignature, numInputs) + for i, sig := range clsagSigs { + if i >= numInputs { + break + } + clsag, _, err := crypto.DeserializeCLSAG(sig.Signature, tx.RingSize) + if err != nil { + return fmt.Errorf("failed to deserialize CLSAG %d: %w", i, err) + } + tx.CLSAGs[i] = clsag + } + tx.CLSAGContexts = nil + tx.signingPhase = 2 + return nil +} + +// AdditionalSighashes returns the CLSAG signing requests after key images are set. +func (tx *Tx) AdditionalSighashes() ([]*xc.SignatureRequest, error) { + if tx.signingPhase != 1 || len(tx.CLSAGContexts) == 0 { + return nil, nil // no more sighashes needed + } + + // Now that key images are set, recompute the CLSAG message from the blob + blob, _ := tx.Serialize() + clsagMessage := computeCLSAGMessage(blob, len(tx.Inputs), len(tx.Outputs)) + + requests := make([]*xc.SignatureRequest, len(tx.CLSAGContexts)) + for i, ctx := range tx.CLSAGContexts { + ringKeys := make([]string, len(ctx.Ring)) + ringCmts := make([]string, len(ctx.CNonzero)) + for j := range ctx.Ring { + ringKeys[j] = hex.EncodeToString(ctx.Ring[j].Bytes()) + ringCmts[j] = hex.EncodeToString(ctx.CNonzero[j].Bytes()) + } + zKey := edwards25519.NewScalar().Subtract(ctx.InputMask, ctx.PseudoMask) + + sh := &MoneroSighash{ + Message: clsagMessage, + RingKeys: ringKeys, + RingCommitments: ringCmts, + COffset: hex.EncodeToString(ctx.COffset.Bytes()), + RealPos: ctx.RealPos, + ZKey: hex.EncodeToString(zKey.Bytes()), + OutputKey: ctx.OutputKey, + TxPubKey: ctx.TxPubKeyHex, + OutputIndex: ctx.OutputIndex, + RngSeed: ctx.RngSeed, + } + requests[i] = &xc.SignatureRequest{ + Payload: EncodeSighash(sh), + } + } + return requests, nil +} + +// computeCLSAGMessage computes the three-hash CLSAG message from a serialized blob. +func computeCLSAGMessage(blob []byte, numInputs, numOutputs int) []byte { + pos := 0 + readVarint := func() uint64 { + v := uint64(0); s := uint(0) + for blob[pos]&0x80 != 0 { v |= uint64(blob[pos]&0x7f) << s; s += 7; pos++ } + v |= uint64(blob[pos]) << s; pos++ + return v + } + + readVarint(); readVarint() + numIn := readVarint() + for i := uint64(0); i < numIn; i++ { + pos++; readVarint() + count := readVarint() + for j := uint64(0); j < count; j++ { readVarint() } + pos += 32 + } + numOut := readVarint() + for i := uint64(0); i < numOut; i++ { + readVarint(); tag := blob[pos]; pos++; pos += 32 + if tag == 0x03 { pos++ } + } + extraLen := readVarint(); pos += int(extraLen) + prefixEnd := pos + + pos++; readVarint() + pos += int(numOut)*8 + int(numOut)*32 + rctBaseEnd := pos + + readVarint() // nbp + kvStart := pos + pos += 6*32 + nL := int(readVarint()); pos += nL*32 + nR := int(readVarint()); pos += nR*32 + + var kv []byte + kvPos := kvStart + kv = append(kv, blob[kvPos:kvPos+6*32]...) + kvPos += 6*32 + for blob[kvPos]&0x80 != 0 { kvPos++ }; kvPos++ + kv = append(kv, blob[kvPos:kvPos+nL*32]...) + kvPos += nL*32 + for blob[kvPos]&0x80 != 0 { kvPos++ }; kvPos++ + kv = append(kv, blob[kvPos:kvPos+nR*32]...) + + ph := crypto.Keccak256(blob[:prefixEnd]) + bh := crypto.Keccak256(blob[prefixEnd:rctBaseEnd]) + kh := crypto.Keccak256(kv) + return crypto.Keccak256(append(append(ph, bh...), kh...)) +} + +// CLSAGMessage computes the three-hash message that CLSAG signs: +// H(prefix_hash || H(rct_sig_base) || H(bp_prunable_kv)) +// +// The rct_base hash must match what would be parsed from the serialized blob. +// The bp_prunable hash uses only the BP+ key fields (not CLSAG or pseudoOuts). +func (tx *Tx) CLSAGMessage() []byte { + // Serialize the full tx to get exact byte boundaries + prefix := tx.SerializePrefix() + rctBase := tx.SerializeRctBase() + bpKv := tx.SerializeBpPrunable() // BP+ key fields only + + prefixHash := crypto.Keccak256(prefix) + rctBaseHash := crypto.Keccak256(rctBase) + bpKvHash := crypto.Keccak256(bpKv) + + combined := make([]byte, 0, 96) + combined = append(combined, prefixHash...) + combined = append(combined, rctBaseHash...) + combined = append(combined, bpKvHash...) + return crypto.Keccak256(combined) +} + +// Serialize produces the full transaction in Monero's wire format. +func (tx *Tx) Serialize() ([]byte, error) { + var buf []byte + + // Transaction prefix + buf = append(buf, tx.SerializePrefix()...) + + // RCT base (inline, not length-prefixed) + buf = append(buf, tx.SerializeRctBase()...) + + // RCT prunable + buf = append(buf, tx.SerializeRctPrunable()...) + + return buf, nil +} + +func (tx *Tx) SerializePrefix() []byte { + var buf []byte + buf = append(buf, crypto.VarIntEncode(uint64(tx.Version))...) + buf = append(buf, crypto.VarIntEncode(tx.UnlockTime)...) + + buf = append(buf, crypto.VarIntEncode(uint64(len(tx.Inputs)))...) + for _, in := range tx.Inputs { + buf = append(buf, 0x02) // txin_to_key + buf = append(buf, crypto.VarIntEncode(in.Amount)...) + buf = append(buf, crypto.VarIntEncode(uint64(len(in.KeyOffsets)))...) + for _, offset := range in.KeyOffsets { + buf = append(buf, crypto.VarIntEncode(offset)...) + } + buf = append(buf, in.KeyImage...) + } + + buf = append(buf, crypto.VarIntEncode(uint64(len(tx.Outputs)))...) + for _, out := range tx.Outputs { + buf = append(buf, crypto.VarIntEncode(out.Amount)...) + buf = append(buf, 0x03) // txout_to_tagged_key + buf = append(buf, out.PublicKey...) + buf = append(buf, out.ViewTag) + } + + buf = append(buf, crypto.VarIntEncode(uint64(len(tx.Extra)))...) + buf = append(buf, tx.Extra...) + + return buf +} + +// PrefixHash = Keccak256(serialized prefix) +func (tx *Tx) PrefixHash() []byte { + return crypto.Keccak256(tx.SerializePrefix()) +} + +// serializeRctBase: type || varint(fee) || ecdhInfo(8 bytes each) || outPk(32 bytes each) +func (tx *Tx) SerializeRctBase() []byte { + var buf []byte + buf = append(buf, tx.RctType) + if tx.RctType == 0 { + return buf + } + + buf = append(buf, crypto.VarIntEncode(tx.Fee)...) + + // ecdhInfo: 8 bytes per output (truncated amount) + for _, ecdh := range tx.EcdhInfo { + if len(ecdh) >= 8 { + buf = append(buf, ecdh[:8]...) + } else { + padded := make([]byte, 8) + copy(padded, ecdh) + buf = append(buf, padded...) + } + } + + // outPk: 32-byte commitment per output + for _, c := range tx.OutCommitments { + buf = append(buf, c.Bytes()...) + } + + return buf +} + +// serializeBpPrunable: the BP+ proof fields as raw keys for hashing. +// This matches get_pre_mlsag_hash's kv construction for RCTTypeBulletproofPlus. +func (tx *Tx) SerializeBpPrunable() []byte { + if tx.BpPlusNative != nil { + var kv []byte + bp := tx.BpPlusNative + kv = append(kv, bp.A[:]...) + kv = append(kv, bp.A1[:]...) + kv = append(kv, bp.B[:]...) + kv = append(kv, bp.R1[:]...) + kv = append(kv, bp.S1[:]...) + kv = append(kv, bp.D1[:]...) + for _, l := range bp.L { + kv = append(kv, l[:]...) + } + for _, r := range bp.R { + kv = append(kv, r[:]...) + } + return kv + } + if tx.BpPlus == nil { + return nil + } + var kv []byte + bp := tx.BpPlus + kv = append(kv, bp.A.Bytes()...) + kv = append(kv, bp.A1.Bytes()...) + kv = append(kv, bp.B.Bytes()...) + kv = append(kv, bp.R1.Bytes()...) + kv = append(kv, bp.S1.Bytes()...) + kv = append(kv, bp.D1.Bytes()...) + for _, l := range bp.L { + kv = append(kv, l.Bytes()...) + } + for _, r := range bp.R { + kv = append(kv, r.Bytes()...) + } + return kv +} + +// serializeRctPrunable: BP+ proof (with size-prefixed L/R) || CLSAGs || pseudoOuts +func (tx *Tx) SerializeRctPrunable() []byte { + var buf []byte + + // BP+ proof + if tx.BpPlusNative != nil { + buf = append(buf, crypto.VarIntEncode(1)...) // 1 proof + bp := tx.BpPlusNative + buf = append(buf, bp.A[:]...) + buf = append(buf, bp.A1[:]...) + buf = append(buf, bp.B[:]...) + buf = append(buf, bp.R1[:]...) + buf = append(buf, bp.S1[:]...) + buf = append(buf, bp.D1[:]...) + buf = append(buf, crypto.VarIntEncode(uint64(len(bp.L)))...) + for _, l := range bp.L { + buf = append(buf, l[:]...) + } + buf = append(buf, crypto.VarIntEncode(uint64(len(bp.R)))...) + for _, r := range bp.R { + buf = append(buf, r[:]...) + } + } else if tx.BpPlus != nil { + buf = append(buf, crypto.VarIntEncode(1)...) + bp := tx.BpPlus + buf = append(buf, bp.A.Bytes()...) + buf = append(buf, bp.A1.Bytes()...) + buf = append(buf, bp.B.Bytes()...) + buf = append(buf, bp.R1.Bytes()...) + buf = append(buf, bp.S1.Bytes()...) + buf = append(buf, bp.D1.Bytes()...) + buf = append(buf, crypto.VarIntEncode(uint64(len(bp.L)))...) + for _, l := range bp.L { + buf = append(buf, l.Bytes()...) + } + buf = append(buf, crypto.VarIntEncode(uint64(len(bp.R)))...) + for _, r := range bp.R { + buf = append(buf, r.Bytes()...) + } + } + + // CLSAGs: s[0..ring_size-1] || c1 || D (NO size prefix on s[]) + for _, clsag := range tx.CLSAGs { + for _, s := range clsag.S { + buf = append(buf, s.Bytes()...) + } + buf = append(buf, clsag.C1.Bytes()...) + buf = append(buf, clsag.D.Bytes()...) + } + + // pseudoOuts + for _, po := range tx.PseudoOuts { + buf = append(buf, po.Bytes()...) + } + + return buf +} diff --git a/chain/monero/tx_input/tx_input.go b/chain/monero/tx_input/tx_input.go new file mode 100644 index 00000000..ab47aca9 --- /dev/null +++ b/chain/monero/tx_input/tx_input.go @@ -0,0 +1,134 @@ +package tx_input + +import ( + xc "github.com/cordialsys/crosschain" + "github.com/cordialsys/crosschain/factory/drivers/registry" + "github.com/shopspring/decimal" +) + +func init() { + registry.RegisterTxBaseInput(&TxInput{}) +} + +type TxInput struct { + xc.TxInputEnvelope + + // Current block height + BlockHeight uint64 `json:"block_height"` + + // Per-byte fee from fee estimation + PerByteFee uint64 `json:"per_byte_fee"` + + // Quantization mask for fee rounding + QuantizationMask uint64 `json:"quantization_mask"` + + // Spendable outputs owned by this wallet (used for building transactions) + Outputs []Output `json:"outputs"` + + // RngSeed is a 32-byte seed for deterministic randomness in the builder. + // Set by the client during FetchTransferInput. + RngSeed []byte `json:"rng_seed"` + + // Cached BP+ proof bytes (from first Transfer() call, reused for determinism) + CachedBpProof []byte `json:"cached_bp_proof,omitempty"` +} + +// Output represents a spendable output in the Monero UTXO model +type Output struct { + // Amount in atomic units (piconero) + Amount uint64 `json:"amount"` + // Output index in the transaction + Index uint64 `json:"index"` + // Transaction hash this output belongs to + TxHash string `json:"tx_hash"` + // Global output index on the blockchain + GlobalIndex uint64 `json:"global_index"` + // The one-time public key for this output + PublicKey string `json:"public_key"` + // RingCT commitment for this output (from get_outs) + Commitment string `json:"commitment,omitempty"` + // TxPubKey is the transaction public key R (hex), needed for key derivation + TxPubKey string `json:"tx_pub_key,omitempty"` + // CommitmentMask is the pre-computed Pedersen commitment mask (hex). + // Computed by the client during scanning: H_s("commitment_mask" || shared_scalar) + CommitmentMask string `json:"commitment_mask,omitempty"` + // Ring members (decoys) for this output, populated by FetchTransferInput + RingMembers []RingMember `json:"ring_members,omitempty"` +} + +// RingMember represents a decoy output in the ring +type RingMember struct { + GlobalIndex uint64 `json:"global_index"` + PublicKey string `json:"public_key"` + Commitment string `json:"commitment"` +} + +func NewTxInput() *TxInput { + return &TxInput{ + TxInputEnvelope: xc.TxInputEnvelope{ + Type: xc.DriverMonero, + }, + } +} + +func (input *TxInput) GetDriver() xc.Driver { + return xc.DriverMonero +} + +func (input *TxInput) SetGasFeePriority(priority xc.GasFeePriority) error { + multiplier, err := priority.GetDefault() + if err != nil { + return err + } + multipliedFee := multiplier.Mul(decimal.NewFromInt(int64(input.PerByteFee))) + input.PerByteFee = uint64(multipliedFee.IntPart()) + return nil +} + +func (input *TxInput) GetFeeLimit() (xc.AmountBlockchain, xc.ContractAddress) { + // Estimate fee as per_byte_fee * estimated_tx_size (2000 bytes typical) + estimatedSize := uint64(2000) + fee := input.PerByteFee * estimatedSize + if input.QuantizationMask > 0 { + fee = (fee + input.QuantizationMask - 1) / input.QuantizationMask * input.QuantizationMask + } + return xc.NewAmountBlockchainFromUint64(fee), "" +} + +func (input *TxInput) IsFeeLimitAccurate() bool { + return false +} + +func (input *TxInput) IndependentOf(other xc.TxInput) (independent bool) { + if otherMonero, ok := other.(*TxInput); ok { + // Independent if they don't share outputs + myOutputs := make(map[string]bool) + for _, o := range input.Outputs { + myOutputs[o.TxHash+":"+string(rune(o.Index))] = true + } + for _, o := range otherMonero.Outputs { + if myOutputs[o.TxHash+":"+string(rune(o.Index))] { + return false + } + } + return true + } + return false +} + +func (input *TxInput) SafeFromDoubleSend(other xc.TxInput) (safe bool) { + if otherMonero, ok := other.(*TxInput); ok { + // Safe if they use the same outputs (key images would be the same) + if len(input.Outputs) != len(otherMonero.Outputs) { + return false + } + for i := range input.Outputs { + if input.Outputs[i].TxHash != otherMonero.Outputs[i].TxHash || + input.Outputs[i].Index != otherMonero.Outputs[i].Index { + return false + } + } + return true + } + return false +} diff --git a/chain/monero/validate.go b/chain/monero/validate.go new file mode 100644 index 00000000..00e8e580 --- /dev/null +++ b/chain/monero/validate.go @@ -0,0 +1,33 @@ +package monero + +import ( + "fmt" + + xc "github.com/cordialsys/crosschain" + "github.com/cordialsys/crosschain/chain/monero/crypto" +) + +func ValidateAddress(cfg *xc.ChainBaseConfig, address xc.Address) error { + addr := string(address) + + // Monero mainnet addresses are 95 characters (standard) or 106 characters (integrated) + if len(addr) != 95 && len(addr) != 106 { + return fmt.Errorf("invalid monero address length: got %d, expected 95 or 106", len(addr)) + } + + prefix, _, _, err := crypto.DecodeAddress(addr) + if err != nil { + return fmt.Errorf("invalid monero address: %w", err) + } + + // Check valid prefix + switch prefix { + case crypto.MainnetAddressPrefix, crypto.MainnetIntegratedPrefix, crypto.MainnetSubaddressPrefix, + crypto.TestnetAddressPrefix, crypto.TestnetIntegratedPrefix, crypto.TestnetSubaddressPrefix: + // valid + default: + return fmt.Errorf("invalid monero address prefix: %d", prefix) + } + + return nil +} diff --git a/chain/solana/tx_input/tx_input.go b/chain/solana/tx_input/tx_input.go index 738126a1..265d2137 100644 --- a/chain/solana/tx_input/tx_input.go +++ b/chain/solana/tx_input/tx_input.go @@ -43,7 +43,8 @@ type GetTxInfo interface { type GetDurableNonceInfo interface { GetDurableNonceAccount() solana.PublicKey GetDurableNonceValue() solana.Hash - IsDurableNonceEnabled() bool + HasDurableNonce() bool + IsCreatingDurableNonceAccount() bool } type TokenAccount struct { @@ -85,8 +86,8 @@ func (input *TxInput) GetDurableNonceValue() solana.Hash { return input.DurableNonce } -func (input *TxInput) IsDurableNonceEnabled() bool { - return input.HasDurableNonce() +func (input *TxInput) IsCreatingDurableNonceAccount() bool { + return input.ShouldCreateDurableNonce && !input.DurableNonceAccount.IsZero() } // GetBlockhashForTx returns the blockhash to use for the transaction. @@ -104,7 +105,7 @@ func (input *TxInput) GetDriver() xc.Driver { } // Solana recent-block-hash timeout margin -const SafetyTimeoutMargin = (5 * time.Minute) +const SafetyTimeoutMargin = (10 * time.Minute) // Returns the microlamports to set the compute budget unit price. // It will not go about the max price amount for safety concerns. @@ -156,31 +157,35 @@ func (input *TxInput) IsFeeLimitAccurate() bool { func (input *TxInput) IndependentOf(other xc.TxInput) (independent bool) { if otherNonce, ok := other.(GetDurableNonceInfo); ok { - // With durable nonces, two transactions conflict only if they use the same nonce value. - // Same nonce account + same nonce value = NOT independent (only one can succeed). - // Same nonce account + different nonce value = independent (each uses its own nonce). - if input.HasDurableNonce() && otherNonce.IsDurableNonceEnabled() { - if input.DurableNonceAccount.Equals(otherNonce.GetDurableNonceAccount()) { + sameAccount := !input.DurableNonceAccount.IsZero() && + input.DurableNonceAccount.Equals(otherNonce.GetDurableNonceAccount()) + if sameAccount { + // Both creating the same nonce account = conflict + if input.IsCreatingDurableNonceAccount() && otherNonce.IsCreatingDurableNonceAccount() { + return false + } + // Both using the same nonce value = conflict (only one can succeed) + // Different nonce values = independent (each uses its own nonce) + if input.HasDurableNonce() && otherNonce.HasDurableNonce() { return !input.DurableNonce.Equals(otherNonce.GetDurableNonceValue()) } } } - // no conflicts on solana as txs are easily parallelizeable through - // the recent-block-hash mechanism. return true } func (input *TxInput) SafeFromDoubleSend(other xc.TxInput) (safe bool) { - // When using durable nonces: the nonce can only be consumed once. - // Same nonce value = SAFE (only one transaction can land, the runtime rejects duplicates). - // Different nonce values = NOT SAFE (both could land, causing a double-send). if otherNonce, ok := other.(GetDurableNonceInfo); ok { - if input.HasDurableNonce() && otherNonce.IsDurableNonceEnabled() { - if input.DurableNonceAccount.Equals(otherNonce.GetDurableNonceAccount()) { - // Same nonce account + same nonce value = only one tx can succeed + sameAccount := !input.DurableNonceAccount.IsZero() && + input.DurableNonceAccount.Equals(otherNonce.GetDurableNonceAccount()) + if sameAccount { + // Safe only when both have actual nonce values and they match + // (the nonce can only be consumed once, so only one tx can land). + // If either is missing a nonce (e.g. setup phase), not safe. + if input.HasDurableNonce() && otherNonce.HasDurableNonce() { return input.DurableNonce.Equals(otherNonce.GetDurableNonceValue()) } - // Different nonce accounts: both could land, check normal timeout + return false } } @@ -190,13 +195,9 @@ func (input *TxInput) SafeFromDoubleSend(other xc.TxInput) (safe bool) { return false } diff := input.Timestamp - oldInput.GetTimestamp() - // solana blockhash lasts only ~1 minute -> we'll require a 5 min period - // and different hash to consider it safe from double-send. if diff < int64(SafetyTimeoutMargin.Seconds()) || oldInput.GetRecentBlockhash().Equals(input.GetRecentBlockhash()) { - // not yet safe return false } - // all timed out - we're safe return true } diff --git a/chain/solana/tx_input/tx_input_test.go b/chain/solana/tx_input/tx_input_test.go index 01323669..947ff1db 100644 --- a/chain/solana/tx_input/tx_input_test.go +++ b/chain/solana/tx_input/tx_input_test.go @@ -130,6 +130,40 @@ func TestTxInputConflicts(t *testing.T) { independent: false, doubleSpendSafe: true, }, + { + // durable nonce setup: both creating the same nonce account = NOT independent + newInput: &TxInput{ + RecentBlockHash: solana.Hash([32]byte{1}), + Timestamp: startTime, + DurableNonceAccount: solana.MustPublicKeyFromBase58("11111111111111111111111111111112"), + ShouldCreateDurableNonce: true, + }, + oldInput: &TxInput{ + RecentBlockHash: solana.Hash([32]byte{2}), + Timestamp: startTime, + DurableNonceAccount: solana.MustPublicKeyFromBase58("11111111111111111111111111111112"), + ShouldCreateDurableNonce: true, + }, + independent: false, + doubleSpendSafe: false, + }, + { + // durable nonce setup: creating different nonce accounts = independent + newInput: &TxInput{ + RecentBlockHash: solana.Hash([32]byte{1}), + Timestamp: startTime, + DurableNonceAccount: solana.MustPublicKeyFromBase58("11111111111111111111111111111112"), + ShouldCreateDurableNonce: true, + }, + oldInput: &TxInput{ + RecentBlockHash: solana.Hash([32]byte{2}), + Timestamp: startTime, + DurableNonceAccount: solana.MustPublicKeyFromBase58("11111111111111111111111111111113"), + ShouldCreateDurableNonce: true, + }, + independent: true, + doubleSpendSafe: false, + }, { // durable nonce: different nonce accounts = independent newInput: &TxInput{ diff --git a/factory/defaults/chains/mainnet.yaml b/factory/defaults/chains/mainnet.yaml index dea32116..afdb752a 100644 --- a/factory/defaults/chains/mainnet.yaml +++ b/factory/defaults/chains/mainnet.yaml @@ -1638,6 +1638,25 @@ chains: asset_id: stellar indexing_co: chain_id: stellar + XMR: + chain: XMR + support: + fee: + accurate: false + driver: monero + chain_name: Monero + decimals: 12 + fee_limit: "0.0001" + confirmations_final: 10 + native_assets: + - asset_id: "XMR" + decimals: 12 + fee_limit: "0.0001" + external: + coin_gecko: + asset_id: monero + coin_market_cap: + asset_id: "328" ZETA: chain: ZETA support: diff --git a/factory/defaults/chains/testnet.yaml b/factory/defaults/chains/testnet.yaml index 0b2241e3..aff2354b 100644 --- a/factory/defaults/chains/testnet.yaml +++ b/factory/defaults/chains/testnet.yaml @@ -944,6 +944,17 @@ chains: gas_budget_default: "0.1" decimals: 18 fee_limit: "100.0" + XMR: + chain: XMR + support: + fee: + accurate: false + driver: monero + chain_name: Monero Testnet + decimals: 12 + fee_limit: "0.01" + confirmations_final: 5 + chain_id: "testnet" 0G: chain: 0G support: diff --git a/factory/drivers/factory.go b/factory/drivers/factory.go index ebadff2b..b88dd015 100644 --- a/factory/drivers/factory.go +++ b/factory/drivers/factory.go @@ -11,6 +11,10 @@ import ( kaspaaddress "github.com/cordialsys/crosschain/chain/kaspa/address" kaspabuilder "github.com/cordialsys/crosschain/chain/kaspa/builder" kaspaclient "github.com/cordialsys/crosschain/chain/kaspa/client" + monero "github.com/cordialsys/crosschain/chain/monero" + moneroaddress "github.com/cordialsys/crosschain/chain/monero/address" + monerobuilder "github.com/cordialsys/crosschain/chain/monero/builder" + moneroclient "github.com/cordialsys/crosschain/chain/monero/client" "github.com/cordialsys/crosschain/chain/near" "github.com/cordialsys/crosschain/chain/substrate" xrpbuilder "github.com/cordialsys/crosschain/chain/xrp/builder" @@ -146,6 +150,8 @@ func NewClient(cfg *xc.ChainConfig, driver xc.Driver) (xclient.Client, error) { return zcash.NewClient(cfg) case xc.DriverHedera: return hederaclient.NewClient(cfg) + case xc.DriverMonero: + return moneroclient.NewClient(cfg) } return nil, fmt.Errorf("no client defined for chain: %s", string(cfg.GetChain().Chain)) } @@ -246,6 +252,8 @@ func NewTxBuilder(cfg *xc.ChainBaseConfig) (xcbuilder.FullTransferBuilder, error return zcash.NewTxBuilder(cfg) case xc.DriverHedera: return hederabuilder.NewTxBuilder(cfg) + case xc.DriverMonero: + return monerobuilder.NewTxBuilder(cfg) } return nil, fmt.Errorf("no tx-builder defined for: %s", string(cfg.Chain)) } @@ -306,6 +314,8 @@ func NewAddressBuilder(cfg *xc.ChainBaseConfig, options ...xcaddress.AddressOpti return zcashaddress.NewAddressBuilder(cfg) case xc.DriverHedera: return hederaaddress.NewAddressBuilder(cfg) + case xc.DriverMonero: + return moneroaddress.NewAddressBuilder(cfg, options...) } return nil, fmt.Errorf("no address builder defined for: %s", string(cfg.Chain)) } @@ -365,6 +375,8 @@ func CheckError(driver xc.Driver, err error) errors.Status { return nearerrors.CheckError(err) case xc.DriverEGLD: return egld.CheckError(err) + case xc.DriverMonero: + return monero.CheckError(err) } return errors.UnknownError } @@ -428,6 +440,8 @@ func ValidateAddress(cfg *xc.ChainBaseConfig, addr xc.Address) error { return near.ValidateAddress(cfg, addr) case xc.DriverEGLD: return egld.ValidateAddress(cfg, addr) + case xc.DriverMonero: + return monero.ValidateAddress(cfg, addr) } return fmt.Errorf("%w: %s", ErrNoAddressValidation, string(cfg.Chain)) } diff --git a/factory/signer/signer.go b/factory/signer/signer.go index 7a876870..12abd63c 100644 --- a/factory/signer/signer.go +++ b/factory/signer/signer.go @@ -20,6 +20,7 @@ import ( "github.com/cordialsys/crosschain/address" cosmostypes "github.com/cordialsys/crosschain/chain/cosmos/types" "github.com/cordialsys/crosschain/chain/dusk" + moneroCrypto "github.com/cordialsys/crosschain/chain/monero/crypto" cosmoscrypto "github.com/cosmos/cosmos-sdk/crypto" "github.com/cosmos/cosmos-sdk/crypto/hd" "github.com/cosmos/cosmos-sdk/crypto/keyring" @@ -148,6 +149,13 @@ func New(driver xc.Driver, secret string, cfgMaybe *xc.ChainBaseConfig, options switch alg { case xc.Ed255: + // Monero uses raw scalars for key derivation (spend key → view key) + if driver == xc.DriverMonero { + if len(secretBz) == 32 { + return &Signer{driver, secretBz, alg}, nil + } + return nil, fmt.Errorf("monero key must be 32 bytes, got %d bytes", len(secretBz)) + } if val := os.Getenv(EnvEd25519ScalarSigning); val == "1" || val == "true" { if len(secretBz) != 32 { return nil, fmt.Errorf("scalar must be 32 bytes, got %d bytes", len(secretBz)) @@ -196,6 +204,20 @@ func (s *Signer) Sign(req *xc.SignatureRequest) (*xc.SignatureResponse, error) { data := req.Payload switch s.algorithm { case xc.Ed255: + // Monero two-phase signing: + // Phase 1: payload has OutputKey/TxPubKey/OutputIndex → return key image (32 bytes) + // Phase 2: payload has full CLSAG context → return CLSAG signature + if s.driver == xc.DriverMonero { + sig, err := moneroCrypto.SignCLSAGFromPayload(data, s.privateKey) + if err != nil { + return nil, fmt.Errorf("monero signing failed: %w", err) + } + return &xc.SignatureResponse{ + Address: "", + Signature: sig, + PublicKey: s.MustPublicKey(), + }, nil + } var signatureRaw []byte if val := os.Getenv(EnvEd25519ScalarSigning); val == "1" || val == "true" { logrus.Debug("using raw scalar signing for ed25519 key") @@ -286,6 +308,17 @@ func (s *Signer) MustSignAll(data []*xc.SignatureRequest) []*xc.SignatureRespons func (s *Signer) PublicKey() (PublicKey, error) { switch s.algorithm { case xc.Ed255: + // Monero: derive both public spend and public view keys + if s.driver == xc.DriverMonero { + _, _, pubSpend, pubView, err := moneroCrypto.DeriveKeysFromSpend(s.privateKey) + if err != nil { + return nil, fmt.Errorf("failed to derive monero keys: %w", err) + } + combined := make([]byte, 64) + copy(combined[:32], pubSpend) + copy(combined[32:], pubView) + return PublicKey(combined), nil + } privateKey := ed25519.PrivateKey(s.privateKey) publicKey := privateKey.Public().(ed25519.PublicKey)