Reduce allocations on ephemeral public key verification
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
DataHoarder 2023-05-11 09:51:00 +02:00
parent 0d7c208e06
commit 6a690ad278
Signed by: DataHoarder
SSH key fingerprint: SHA256:OLTRf6Fl87G52SiR7sWLGNzlJt4WOX+tfI2yxo0z7xk
7 changed files with 81 additions and 19 deletions

View file

@ -7,6 +7,7 @@ import (
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/crypto"
p2poolcrypto "git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/crypto"
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
"hash"
"strings"
)
@ -36,13 +37,28 @@ func getEphemeralPublicKeyInline(spendPub, viewPub *edwards25519.Point, txKey *e
}
func GetEphemeralPublicKeyAndViewTag(a Interface, txKey crypto.PrivateKey, outputIndex uint64) (crypto.PublicKey, uint8) {
pK, viewTag := crypto.GetDerivationSharedDataAndViewTagForOutputIndexNoAllocate(txKey.GetDerivationCofactor(a.ViewPublicKey()), outputIndex)
return GetPublicKeyForSharedData(a, crypto.PrivateKeyFromScalar(&pK)), viewTag
pK, viewTag := crypto.GetDerivationSharedDataAndViewTagForOutputIndex(txKey.GetDerivationCofactor(a.ViewPublicKey()), outputIndex)
return GetPublicKeyForSharedData(a, pK), viewTag
}
func GetEphemeralPublicKeyAndViewTagNoAllocate(a Interface, txKey crypto.PrivateKey, outputIndex uint64) (crypto.PublicKeyBytes, uint8) {
pK, viewTag := crypto.GetDerivationSharedDataAndViewTagForOutputIndexNoAllocate(txKey.GetDerivationCofactor(a.ViewPublicKey()), outputIndex)
return GetPublicKeyForSharedData(a, crypto.PrivateKeyFromScalar(&pK)).AsBytes(), viewTag
// GetEphemeralPublicKeyAndViewTagNoAllocate Special version of GetEphemeralPublicKeyAndViewTag
func GetEphemeralPublicKeyAndViewTagNoAllocate(a *PackedAddress, txKey *crypto.PrivateKeyScalar, outputIndex uint64, hasher hash.Hash) (crypto.PublicKeyBytes, uint8) {
scalar := txKey.Scalar()
var spendPublicKeyPoint, viewPublicKeyPoint, point, cofactor, intermediatePublicKey, ephemeralPublicKey edwards25519.Point
_, _ = spendPublicKeyPoint.SetBytes((*a)[0][:])
_, _ = viewPublicKeyPoint.SetBytes((*a)[1][:])
point.ScalarMult(scalar, &viewPublicKeyPoint)
cofactor.MultByCofactor(&point)
pK, viewTag := crypto.GetDerivationSharedDataAndViewTagForOutputIndexNoAllocate(crypto.PublicKeyBytes(cofactor.Bytes()), outputIndex, hasher)
intermediatePublicKey.ScalarBaseMult(&pK)
ephemeralPublicKey.Add(&intermediatePublicKey, &spendPublicKeyPoint)
var ephemeralPublicKeyBytes crypto.PublicKeyBytes
copy(ephemeralPublicKeyBytes[:], ephemeralPublicKey.Bytes())
return ephemeralPublicKeyBytes, viewTag
}
func GetTxProofV2(a Interface, txId types.Hash, txKey crypto.PrivateKey, message string) string {

View file

@ -3,6 +3,8 @@ package crypto
import (
"encoding/binary"
"filippo.io/edwards25519"
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
"hash"
)
func GetDerivationSharedDataForOutputIndex(derivation PublicKey, outputIndex uint64) PrivateKey {
@ -11,10 +13,12 @@ func GetDerivationSharedDataForOutputIndex(derivation PublicKey, outputIndex uin
return PrivateKeyFromScalar(HashToScalar(k[:], varIntBuf[:binary.PutUvarint(varIntBuf[:], outputIndex)]))
}
var viewTagDomain = []byte("view_tag")
func GetDerivationViewTagForOutputIndex(derivation PublicKey, outputIndex uint64) uint8 {
var k = derivation.AsBytes()
var varIntBuf [binary.MaxVarintLen64]byte
return PooledKeccak256([]byte("view_tag"), k[:], varIntBuf[:binary.PutUvarint(varIntBuf[:], outputIndex)])[0]
return PooledKeccak256(viewTagDomain, k[:], varIntBuf[:binary.PutUvarint(varIntBuf[:], outputIndex)])[0]
}
func GetDerivationSharedDataAndViewTagForOutputIndex(derivation PublicKey, outputIndex uint64) (PrivateKey, uint8) {
@ -23,16 +27,30 @@ func GetDerivationSharedDataAndViewTagForOutputIndex(derivation PublicKey, outpu
n := binary.PutUvarint(varIntBuf[:], outputIndex)
pK := PrivateKeyFromScalar(HashToScalar(k[:], varIntBuf[:n]))
return pK, PooledKeccak256([]byte("view_tag"), k[:], varIntBuf[:n])[0]
return pK, PooledKeccak256(viewTagDomain, k[:], varIntBuf[:n])[0]
}
func GetDerivationSharedDataAndViewTagForOutputIndexNoAllocate(derivation PublicKey, outputIndex uint64) (edwards25519.Scalar, uint8) {
// GetDerivationSharedDataAndViewTagForOutputIndexNoAllocate Special version of GetDerivationSharedDataAndViewTagForOutputIndex
func GetDerivationSharedDataAndViewTagForOutputIndexNoAllocate(k PublicKeyBytes, outputIndex uint64, hasher hash.Hash) (edwards25519.Scalar, uint8) {
var buf [PublicKeySize + binary.MaxVarintLen64]byte
var k = derivation.AsBytes()
copy(buf[:], k[:])
n := binary.PutUvarint(buf[PublicKeySize:], outputIndex)
return HashToScalarNoAllocateSingle(buf[:PublicKeySize+n]), Keccak256([]byte("view_tag"), buf[:PublicKeySize+n])[0]
var h types.Hash
hasher.Reset()
hasher.Write(buf[:PublicKeySize+n])
HashFastSum(hasher, h[:])
scReduce32(h[:])
var c edwards25519.Scalar
_, _ = c.SetCanonicalBytes(h[:])
hasher.Reset()
hasher.Write(viewTagDomain)
hasher.Write(buf[:PublicKeySize+n])
HashFastSum(hasher, h[:])
return c, h[0]
}
func GetKeyImage(pair *KeyPair) PublicKey {

View file

@ -63,6 +63,7 @@ func (p *PrivateKeyScalar) GetDerivation(public PublicKey) PublicKey {
func (p *PrivateKeyScalar) GetDerivationCofactor(public PublicKey) PublicKey {
return deriveKeyExchangeSecretCofactor(p, public.AsPoint())
}
func (p *PrivateKeyScalar) String() string {
return hex.EncodeToString(p.Scalar().Bytes())
}

View file

@ -6,6 +6,7 @@ import (
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/crypto"
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
"github.com/floatdrop/lru"
"hash"
"sync/atomic"
)
@ -18,7 +19,7 @@ type ephemeralPublicKeyWithViewTag struct {
}
type DerivationCacheInterface interface {
GetEphemeralPublicKey(a address.Interface, txKeySlice crypto.PrivateKeySlice, txKeyScalar *crypto.PrivateKeyScalar, outputIndex uint64) (crypto.PublicKeyBytes, uint8)
GetEphemeralPublicKey(a *address.PackedAddress, txKeySlice crypto.PrivateKeySlice, txKeyScalar *crypto.PrivateKeyScalar, outputIndex uint64, hasher hash.Hash) (crypto.PublicKeyBytes, uint8)
GetDeterministicTransactionKey(seed types.Hash, prevId types.Hash) *crypto.KeyPair
}
@ -42,7 +43,7 @@ func (d *DerivationCache) Clear() {
d.ephemeralPublicKeyCache.Store(lru.New[ephemeralPublicKeyCacheKey, ephemeralPublicKeyWithViewTag](2000))
}
func (d *DerivationCache) GetEphemeralPublicKey(a address.Interface, txKeySlice crypto.PrivateKeySlice, txKeyScalar *crypto.PrivateKeyScalar, outputIndex uint64) (crypto.PublicKeyBytes, uint8) {
func (d *DerivationCache) GetEphemeralPublicKey(a *address.PackedAddress, txKeySlice crypto.PrivateKeySlice, txKeyScalar *crypto.PrivateKeyScalar, outputIndex uint64, hasher hash.Hash) (crypto.PublicKeyBytes, uint8) {
var key ephemeralPublicKeyCacheKey
copy(key[:], txKeySlice)
copy(key[crypto.PrivateKeySize:], a.ToPackedAddress().Bytes())
@ -51,7 +52,7 @@ func (d *DerivationCache) GetEphemeralPublicKey(a address.Interface, txKeySlice
ephemeralPublicKeyCache := d.ephemeralPublicKeyCache.Load()
if ephemeralPubKey := ephemeralPublicKeyCache.Get(key); ephemeralPubKey == nil {
d.ephemeralPublicKeyCacheMisses.Add(1)
pKB, viewTag := address.GetEphemeralPublicKeyAndViewTagNoAllocate(a, txKeyScalar, outputIndex)
pKB, viewTag := address.GetEphemeralPublicKeyAndViewTagNoAllocate(a, txKeyScalar, outputIndex, hasher)
ephemeralPublicKeyCache.Set(key, ephemeralPublicKeyWithViewTag{PublicKey: pKB, ViewTag: viewTag})
return pKB, viewTag
} else {

View file

@ -4,6 +4,7 @@ import (
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/address"
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/crypto"
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
"hash"
)
type NilDerivationCache struct {
@ -13,8 +14,8 @@ func (d *NilDerivationCache) Clear() {
}
func (d *NilDerivationCache) GetEphemeralPublicKey(a address.Interface, _ crypto.PrivateKeySlice, txKeyScalar *crypto.PrivateKeyScalar, outputIndex uint64) (crypto.PublicKeyBytes, uint8) {
ephemeralPubKey, viewTag := address.GetEphemeralPublicKeyAndViewTag(a, txKeyScalar, outputIndex)
func (d *NilDerivationCache) GetEphemeralPublicKey(a *address.PackedAddress, _ crypto.PrivateKeySlice, txKeyScalar *crypto.PrivateKeyScalar, outputIndex uint64, hasher hash.Hash) (crypto.PublicKeyBytes, uint8) {
ephemeralPubKey, viewTag := address.GetEphemeralPublicKeyAndViewTagNoAllocate(a, txKeyScalar, outputIndex, hasher)
return ephemeralPubKey.AsBytes(), viewTag
}

View file

@ -7,12 +7,14 @@ import (
"git.gammaspectra.live/P2Pool/p2pool-observer/monero"
mainblock "git.gammaspectra.live/P2Pool/p2pool-observer/monero/block"
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/client"
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/crypto"
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/randomx"
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/transaction"
p2pooltypes "git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/types"
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
"git.gammaspectra.live/P2Pool/p2pool-observer/utils"
"golang.org/x/exp/slices"
"hash"
"log"
"sync"
"sync/atomic"
@ -549,19 +551,30 @@ func (c *SideChain) verifyBlock(block *PoolBlock) (verification error, invalid e
txPrivateKeySlice := block.Side.CoinbasePrivateKey.AsSlice()
txPrivateKeyScalar := block.Side.CoinbasePrivateKey.AsScalar()
var hashers []hash.Hash
results := utils.SplitWork(-2, uint64(len(rewards)), func(workIndex uint64, workerIndex int) error {
out := block.Main.Coinbase.Outputs[workIndex]
if rewards[workIndex] != out.Reward {
return fmt.Errorf("has invalid reward at index %d, got %d, expected %d", workIndex, out.Reward, rewards[workIndex])
}
if ephPublicKey, viewTag := c.derivationCache.GetEphemeralPublicKey(&c.sharesCache[workIndex].Address, txPrivateKeySlice, txPrivateKeyScalar, workIndex); ephPublicKey != out.EphemeralPublicKey {
if ephPublicKey, viewTag := c.derivationCache.GetEphemeralPublicKey(&c.sharesCache[workIndex].Address, txPrivateKeySlice, txPrivateKeyScalar, workIndex, hashers[workerIndex]); ephPublicKey != out.EphemeralPublicKey {
return fmt.Errorf("has incorrect eph_public_key at index %d, got %s, expected %s", workIndex, out.EphemeralPublicKey.String(), ephPublicKey.String())
} else if out.Type == transaction.TxOutToTaggedKey && viewTag != out.ViewTag {
return fmt.Errorf("has incorrect view tag at index %d, got %d, expected %d", workIndex, out.ViewTag, viewTag)
}
return nil
}, nil)
}, func(routines, routineIndex int) error {
hashers = append(hashers, crypto.GetKeccak256Hasher())
return nil
})
defer func() {
for _, h := range hashers {
crypto.PutKeccak256Hasher(h)
}
}()
for i := range results {
if results[i] != nil {

View file

@ -12,6 +12,7 @@ import (
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
"git.gammaspectra.live/P2Pool/p2pool-observer/utils"
"golang.org/x/exp/slices"
"hash"
"log"
"lukechampine.com/uint128"
"math"
@ -42,18 +43,29 @@ func CalculateOutputs(block *PoolBlock, consensus *Consensus, difficultyByHeight
txPrivateKeySlice := block.Side.CoinbasePrivateKey.AsSlice()
txPrivateKeyScalar := block.Side.CoinbasePrivateKey.AsScalar()
var hashers []hash.Hash
_ = utils.SplitWork(-2, n, func(workIndex uint64, workerIndex int) error {
output := transaction.Output{
Index: workIndex,
Type: txType,
}
output.Reward = tmpRewards[output.Index]
output.EphemeralPublicKey, output.ViewTag = derivationCache.GetEphemeralPublicKey(&tmpShares[output.Index].Address, txPrivateKeySlice, txPrivateKeyScalar, output.Index)
output.EphemeralPublicKey, output.ViewTag = derivationCache.GetEphemeralPublicKey(&tmpShares[output.Index].Address, txPrivateKeySlice, txPrivateKeyScalar, output.Index, hashers[workerIndex])
outputs[output.Index] = output
return nil
}, nil)
}, func(routines, routineIndex int) error {
hashers = append(hashers, crypto.GetKeccak256Hasher())
return nil
})
defer func() {
for _, h := range hashers {
crypto.PutKeccak256Hasher(h)
}
}()
return outputs, bottomHeight
}