From d5fbd5941b165c991fbbb817285573387a1868db Mon Sep 17 00:00:00 2001 From: Kuldotha Date: Sat, 26 Apr 2025 12:22:34 +0200 Subject: [PATCH 1/2] :zap: Optimize find program key by adding static buffers and reusing the same SHA256 while finding an off-curve key --- .../Orca/Address/AddressUtils.cs | 24 +------ src/Solana.Unity.Wallet/PublicKey.cs | 69 ++++++++++++------- 2 files changed, 46 insertions(+), 47 deletions(-) diff --git a/src/Solana.Unity.Dex/Orca/Address/AddressUtils.cs b/src/Solana.Unity.Dex/Orca/Address/AddressUtils.cs index 94194d91..d3f8fcde 100644 --- a/src/Solana.Unity.Dex/Orca/Address/AddressUtils.cs +++ b/src/Solana.Unity.Dex/Orca/Address/AddressUtils.cs @@ -20,28 +20,8 @@ public static class AddressUtils /// A Pda (program-derived address) off the curve, or null. public static Pda FindProgramAddress(IEnumerable seeds, PublicKey programId) { - byte nonce = 255; - - while (nonce != 0) - { - PublicKey address; - List seedsWithNonce = new List(seeds); - seedsWithNonce.Add(new byte[] { nonce }); - - //try to generate the address - bool created = PublicKey.TryCreateProgramAddress(new List(seedsWithNonce), programId, out address); - - //if succeeded, return - if (created) - { - return new Pda(address, nonce); - } - - //decrease the nonce and retry if failed - nonce--; - } - - return null; + return PublicKey.TryFindProgramAddress(seeds, programId, out PublicKey pubkey, out byte bump) + ? new Pda(pubkey, bump) : null; } /// diff --git a/src/Solana.Unity.Wallet/PublicKey.cs b/src/Solana.Unity.Wallet/PublicKey.cs index fa249849..c0673bbe 100644 --- a/src/Solana.Unity.Wallet/PublicKey.cs +++ b/src/Solana.Unity.Wallet/PublicKey.cs @@ -256,7 +256,7 @@ public static bool IsValid(ReadOnlySpan key, bool validateCurve = false) /// /// The bytes of the `ProgramDerivedAddress` string. /// - private static readonly byte[] ProgramDerivedAddressBytes = Encoding.UTF8.GetBytes("ProgramDerivedAddress"); + private static readonly byte[] ProgramDerivedAddressBytes = "ProgramDerivedAddress"u8.ToArray(); /// /// Derives a program address. @@ -264,36 +264,41 @@ public static bool IsValid(ReadOnlySpan key, bool validateCurve = false) /// The address seeds. /// The program Id. /// The derived public key, returned as inline out. - /// true if it could derive the program address for the given seeds, otherwise false.. + /// true if it could derive the program address for the given seeds, otherwise false. /// Throws exception when one of the seeds has an invalid length. public static bool TryCreateProgramAddress(ICollection seeds, PublicKey programId, out PublicKey publicKey) { - MemoryStream buffer = new(PublicKeyLength * seeds.Count + ProgramDerivedAddressBytes.Length + programId.KeyBytes.Length); - + using SHA256 sha256 = SHA256.Create(); + sha256.Initialize(); + foreach (byte[] seed in seeds) { if (seed.Length > PublicKeyLength) { throw new ArgumentException("max seed length exceeded", nameof(seeds)); } - buffer.Write(seed,0, seed.Length); + + sha256.TransformBlock(seed,0, seed.Length, null, 0); } - buffer.Write(programId.KeyBytes, 0, programId.KeyBytes.Length); - buffer.Write(ProgramDerivedAddressBytes, 0, ProgramDerivedAddressBytes.Length); - - SHA256 sha256 = SHA256.Create(); - byte[] hash = sha256.ComputeHash(new ReadOnlySpan(buffer.GetBuffer(), 0, (int)buffer.Length).ToArray()); - + sha256.TransformBlock(programId.KeyBytes,0, programId.KeyBytes.Length, null,0); + sha256.TransformBlock(ProgramDerivedAddressBytes, 0, ProgramDerivedAddressBytes.Length, null, 0); + sha256.TransformFinalBlock([], 0, 0); + + byte[] hash = sha256.Hash!; if (hash.IsOnCurve()) { publicKey = null; return false; } - publicKey = new(hash); + + publicKey = new PublicKey(hash); return true; } + private static readonly List PdaSeedsBuffer = []; + private static readonly byte[] BumpArray = new byte[1]; + /// /// Attempts to find a program address for the passed seeds and program Id. /// @@ -304,28 +309,42 @@ public static bool TryCreateProgramAddress(ICollection seeds, PublicKey /// True whenever the address for a nonce was found, otherwise false. public static bool TryFindProgramAddress(IEnumerable seeds, PublicKey programId, out PublicKey address, out byte bump) { - byte seedBump = 255; - List buffer = seeds.ToList(); - var bumpArray = new byte[1]; - buffer.Add(bumpArray); + PdaSeedsBuffer.Clear(); + PdaSeedsBuffer.AddRange(seeds); - while (seedBump != 0) + if (PdaSeedsBuffer.Any(seed => seed.Length > PublicKeyLength)) + { + throw new ArgumentException("max seed length exceeded", nameof(seeds)); + } + + using SHA256 sha256 = SHA256.Create(); + for (bump = 255; ; bump--) { - bumpArray[0] = seedBump; - bool success = TryCreateProgramAddress(buffer, programId, out PublicKey derivedAddress); + sha256.Initialize(); - if (success) + foreach (byte[] seed in PdaSeedsBuffer) + { + sha256.TransformBlock(seed, 0, seed.Length, null, 0); + } + + BumpArray[0] = bump; + sha256.TransformBlock(BumpArray, 0, 1, null, 0); + sha256.TransformBlock(programId.KeyBytes, 0, programId.KeyBytes.Length, null, 0); + sha256.TransformBlock(ProgramDerivedAddressBytes, 0, ProgramDerivedAddressBytes.Length, null, 0); + sha256.TransformFinalBlock([], 0, 0); + + byte[] hash = sha256.Hash!; + if (!hash.IsOnCurve()) { - address = derivedAddress; - bump = seedBump; + address = new PublicKey(hash); return true; } - seedBump--; + if (bump == 0) + break; } - address = null; - bump = 0; + address = null!; return false; } From 3a50de52f193e1daf49bdb073569766da6f9612c Mon Sep 17 00:00:00 2001 From: Kuldotha Date: Sat, 26 Apr 2025 12:47:27 +0200 Subject: [PATCH 2/2] :sparkles: Add thread safety --- src/Solana.Unity.Wallet/PublicKey.cs | 46 +++++++++++++++++++--------- 1 file changed, 31 insertions(+), 15 deletions(-) diff --git a/src/Solana.Unity.Wallet/PublicKey.cs b/src/Solana.Unity.Wallet/PublicKey.cs index c0673bbe..92dab57a 100644 --- a/src/Solana.Unity.Wallet/PublicKey.cs +++ b/src/Solana.Unity.Wallet/PublicKey.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Security.Cryptography; using System.Text; +using System.Threading; namespace Solana.Unity.Wallet { @@ -258,6 +259,21 @@ public static bool IsValid(ReadOnlySpan key, bool validateCurve = false) /// private static readonly byte[] ProgramDerivedAddressBytes = "ProgramDerivedAddress"u8.ToArray(); + /// + /// A thread-local buffer for all Pda Seeds taht we can reuse + /// + private static readonly ThreadLocal> PdaSeedsBuffer = new(() => []); + + /// + /// A thread local buffer for the bump array that we can reuse + /// + private static readonly ThreadLocal BumpArray = new(() => new byte[1]); + + /// + /// A thread local sha256 instance that we can reuse + /// + private static readonly ThreadLocal Sha256 = new(SHA256.Create); + /// /// Derives a program address. /// @@ -268,7 +284,7 @@ public static bool IsValid(ReadOnlySpan key, bool validateCurve = false) /// Throws exception when one of the seeds has an invalid length. public static bool TryCreateProgramAddress(ICollection seeds, PublicKey programId, out PublicKey publicKey) { - using SHA256 sha256 = SHA256.Create(); + SHA256 sha256 = Sha256.Value; sha256.Initialize(); foreach (byte[] seed in seeds) @@ -295,10 +311,7 @@ public static bool TryCreateProgramAddress(ICollection seeds, PublicKey publicKey = new PublicKey(hash); return true; } - - private static readonly List PdaSeedsBuffer = []; - private static readonly byte[] BumpArray = new byte[1]; - + /// /// Attempts to find a program address for the passed seeds and program Id. /// @@ -309,30 +322,33 @@ public static bool TryCreateProgramAddress(ICollection seeds, PublicKey /// True whenever the address for a nonce was found, otherwise false. public static bool TryFindProgramAddress(IEnumerable seeds, PublicKey programId, out PublicKey address, out byte bump) { - PdaSeedsBuffer.Clear(); - PdaSeedsBuffer.AddRange(seeds); - - if (PdaSeedsBuffer.Any(seed => seed.Length > PublicKeyLength)) + List pdaSeedsBuffer = PdaSeedsBuffer.Value; + pdaSeedsBuffer.Clear(); + pdaSeedsBuffer.AddRange(seeds); + + if (pdaSeedsBuffer.Any(seed => seed.Length > PublicKeyLength)) { throw new ArgumentException("max seed length exceeded", nameof(seeds)); } - using SHA256 sha256 = SHA256.Create(); + byte[] bumpArray = BumpArray.Value; + SHA256 sha256 = Sha256.Value; + for (bump = 255; ; bump--) { sha256.Initialize(); - foreach (byte[] seed in PdaSeedsBuffer) + foreach (byte[] seed in pdaSeedsBuffer) { sha256.TransformBlock(seed, 0, seed.Length, null, 0); } - - BumpArray[0] = bump; - sha256.TransformBlock(BumpArray, 0, 1, null, 0); + + bumpArray[0] = bump; + sha256.TransformBlock(bumpArray, 0, 1, null, 0); sha256.TransformBlock(programId.KeyBytes, 0, programId.KeyBytes.Length, null, 0); sha256.TransformBlock(ProgramDerivedAddressBytes, 0, ProgramDerivedAddressBytes.Length, null, 0); sha256.TransformFinalBlock([], 0, 0); - + byte[] hash = sha256.Hash!; if (!hash.IsOnCurve()) {