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;