diff --git a/src/neo/SmartContract/InteropService.Contract.cs b/src/neo/SmartContract/InteropService.Contract.cs
index f0fb6bfef1..bb85af6091 100644
--- a/src/neo/SmartContract/InteropService.Contract.cs
+++ b/src/neo/SmartContract/InteropService.Contract.cs
@@ -1,3 +1,4 @@
+using Neo.Cryptography.ECC;
using Neo.IO;
using Neo.Ledger;
using Neo.Persistence;
@@ -20,6 +21,12 @@ public static class Contract
public static readonly InteropDescriptor CallEx = Register("System.Contract.CallEx", Contract_CallEx, 0_01000000, TriggerType.System | TriggerType.Application, CallFlags.AllowCall);
public static readonly InteropDescriptor IsStandard = Register("System.Contract.IsStandard", Contract_IsStandard, 0_00030000, TriggerType.All, CallFlags.None);
+ ///
+ /// Calculate corresponding account scripthash for given public key
+ /// Warning: check first that input public key is valid, before creating the script.
+ ///
+ public static readonly InteropDescriptor CreateStandardAccount = Register("System.Contract.CreateStandardAccount", Contract_CreateStandardAccount, 0_00010000, TriggerType.All, CallFlags.None);
+
private static long GetDeploymentPrice(EvaluationStack stack, StoreView snapshot)
{
int size = stack.Peek(0).GetByteLength() + stack.Peek(1).GetByteLength();
@@ -166,6 +173,14 @@ private static bool Contract_IsStandard(ApplicationEngine engine)
engine.CurrentContext.EvaluationStack.Push(isStandard);
return true;
}
+
+ private static bool Contract_CreateStandardAccount(ApplicationEngine engine)
+ {
+ if (!engine.TryPop(out ReadOnlySpan pubKey)) return false;
+ UInt160 scriptHash = SmartContract.Contract.CreateSignatureRedeemScript(ECPoint.DecodePoint(pubKey, ECCurve.Secp256r1)).ToScriptHash();
+ engine.Push(scriptHash.ToArray());
+ return true;
+ }
}
}
}
diff --git a/tests/neo.UnitTests/SmartContract/UT_InteropService.cs b/tests/neo.UnitTests/SmartContract/UT_InteropService.cs
index 27fd3abd46..bed768d2be 100644
--- a/tests/neo.UnitTests/SmartContract/UT_InteropService.cs
+++ b/tests/neo.UnitTests/SmartContract/UT_InteropService.cs
@@ -861,6 +861,45 @@ public void TestContract_Destroy()
}
+ [TestMethod]
+ public void TestContract_CreateStandardAccount()
+ {
+ var engine = GetEngine(true, true);
+ byte[] data = "024b817ef37f2fc3d4a33fe36687e592d9f30fe24b3e28187dc8f12b3b3b2b839e".HexToBytes();
+
+ engine.CurrentContext.EvaluationStack.Push(data);
+ InteropService.Invoke(engine, InteropService.Contract.CreateStandardAccount).Should().BeTrue();
+ engine.CurrentContext.EvaluationStack.Pop().GetSpan().ToArray().Should().BeEquivalentTo(UInt160.Parse("0x2c847208959ec1cc94dd13bfe231fa622a404a8a").ToArray());
+
+ data = "064b817ef37f2fc3d4a33fe36687e592d9f30fe24b3e28187dc8f12b3b3b2b839e".HexToBytes();
+ engine.CurrentContext.EvaluationStack.Push(data);
+ Assert.ThrowsException(() => InteropService.Invoke(engine, InteropService.Contract.CreateStandardAccount)).Message.Should().BeEquivalentTo("Invalid point encoding 6");
+
+ data = "024b817ef37f2fc3d4a33fe36687e599f30fe24b3e28187dc8f12b3b3b2b839e".HexToBytes();
+ engine.CurrentContext.EvaluationStack.Push(data);
+ Assert.ThrowsException(() => InteropService.Invoke(engine, InteropService.Contract.CreateStandardAccount)).Message.Should().BeEquivalentTo("Incorrect length for compressed encoding");
+
+ data = "02ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff".HexToBytes();
+ engine.CurrentContext.EvaluationStack.Push(data);
+ Assert.ThrowsException(() => InteropService.Invoke(engine, InteropService.Contract.CreateStandardAccount)).Message.Should().BeEquivalentTo("x value too large in field element");
+
+ data = "020fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff".HexToBytes();
+ engine.CurrentContext.EvaluationStack.Push(data);
+ Assert.ThrowsException(() => InteropService.Invoke(engine, InteropService.Contract.CreateStandardAccount)).Message.Should().BeEquivalentTo("Invalid point compression");
+
+ data = "044b817ef37f2fc3d4a33fe36687e592d9f30fe24b3e28187dc8f12b3b3b2b839e".HexToBytes();
+ engine.CurrentContext.EvaluationStack.Push(data);
+ Assert.ThrowsException(() => InteropService.Invoke(engine, InteropService.Contract.CreateStandardAccount)).Message.Should().BeEquivalentTo("Incorrect length for uncompressed/hybrid encoding");
+
+ data = "04ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff".HexToBytes();
+ engine.CurrentContext.EvaluationStack.Push(data);
+ Assert.ThrowsException(() => InteropService.Invoke(engine, InteropService.Contract.CreateStandardAccount)).Message.Should().BeEquivalentTo("x value too large in field element");
+
+ data = "040fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff".HexToBytes();
+ engine.CurrentContext.EvaluationStack.Push(data);
+ Assert.ThrowsException(() => InteropService.Invoke(engine, InteropService.Contract.CreateStandardAccount)).Message.Should().BeEquivalentTo("x value too large in field element");
+ }
+
public static void LogEvent(object sender, LogEventArgs args)
{
Transaction tx = (Transaction)args.ScriptContainer;