-
Notifications
You must be signed in to change notification settings - Fork 1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
add new witness scope #2596
add new witness scope #2596
Conversation
8dd7a2c
to
5015c4f
Compare
Add 2 new sign scope to make CheckWitness more safely by checking CallingScriptHash. CustomCallingContracts set contract with trusted caller contract list. Only Trust Caller or entry call can pass "CheckWitness". For example:
TokenA,TokenB would call CheckWitness. CustomCallingGroups set contract with trusted caller's group list. Only Trust Group Caller or entry call can pass "CheckWitness".
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For example:
[ {TokenA:[Caller1,Caller2,TokenB,...]}, {TokenB:[Caller1,Caller3,...]}, ... ]
TokenA,TokenB would call CheckWitness.
Entry=>TokenA, Entry=>Caller1=>TokenA are ok.
Entry=>Caller3=>TokenA will fail.
Looks interesting, but it made me wonder about Entry -> Caller1 -> TokenA -> BAD -> Caller1 -> TokenA
case. This can happen if for some reason Caller1
needs to move TokenA
for user but it makes a move to BAD
contract address which is invoked via onNEP17Payment
and then does its nasty things. But after some thinking I came to conclusion that in case of CustomCallingContracts
this probably is not a problem, after all Caller1
will still do CheckWitness
too (or is there any legit case it won't do that?) and it won't work for the second invocation (because properly signed transaction will have CalledByEntry, CustomCallingContracts(TokenA:Calle1)
scope which will fail for non-entry invocation of Caller1
).
Though we can also imagine Entry -> Caller1 -> Caller2 -> TokenA -> BAD -> Caller2 -> TokenA
, here CustomCallingContracts
won't help. Maybe it's somewhat of a stretched example, but still, CountedContracts
solves this (at least until the count is one). We may also consider invocation stack position instead of counting, like Caller2 2; TokenA 3
witness which will break for Caller2 5
and TokenA 6
.
But it's even more complex for groups. Because we actually want to give a signature scoped for any in-group invocations, but stopping right after execution path leaving this group. NeoFS side chain contracts are a perfect example of this, there is a number of them and we trust all of them, so whatever they do internally to do the job calling each other is absolutely irrelevant, we want CheckWitness
to work anywhere in Entry -> NeoFS1 -> NeoFS2 -> NeoFS3 -> NeoFS4
, but the moment we have Entry -> NeoFS1 -> non-NeoFS
on our call stack the witness should be revoked and I don't think CustomCallingGroups
allow to express that.
For
|
I thought groups are there to simplify things for us and if I need to specify every group contract in the scope then what's use of a group? |
Yes, I agree. But I can hardly find a way to make simplify and security at the same time now. I'm trying to find a way let you set "ECPoint" or "UInt160" as a key, maybe use two dictionaries? |
CountedGroups solve this. |
…to add-new-scope # Conflicts: # src/neo/Network/P2P/Payloads/Signer.cs # tests/neo.UnitTests/Network/P2P/Payloads/UT_Signers.cs # tests/neo.UnitTests/SmartContract/UT_ApplicationEngine.Runtime.cs
I have refacted the CustomCallingGroups implementation, it can support these input format now(Group Public Key should be converted to AddressHash):
For |
Whoa. Now that's not trivial to explain to people albeit it probably works. Makes me wonder do we need Some obligatory nitpicking:
Maybe we can solve it by allowing both keys and script hashes to be specified (and switching mode accordingly)? |
Do you have more details about switch mode? |
I mean we technically can have a Anyway, |
Do you mean use byte[] instead of UInt160 and ECPoint( |
src/neo/IO/Helper.cs
Outdated
/// <summary> | ||
/// Reads an <see cref="ISerializable"/> Dictionary(TKey,TValue[]) from a <see cref="BinaryReader"/>. | ||
/// </summary> | ||
/// <typeparam name="TKey"></typeparam> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fill the descriptions please
@@ -35,17 +38,22 @@ public class Signer : ISerializable | |||
/// </summary> | |||
public ECPoint[] AllowedGroups; | |||
|
|||
public IDictionary<UInt160, UInt160[]> AllowedCallingContracts; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AllowingCall
and add a boolean for IsGroup
?
I guess any of the two works, it's just an implementation detail. My worries are mostly about the protocol, we either allow specifying hash or public key in items or only use hashes and calculate them on the fly for keys. And then we'll need to explain all of that to developers. I'm not sure I've seen groups being identified by script hashes anywhere, so I guess developers would prefer using keys for group identification and if we're to allow using them in the protocol it'd make things somewhat simpler for people trying to construct these transactions. |
public override int GetHashCode() | ||
{ | ||
if (Data == null) return 0; | ||
if (Data.Length >= 4) return BitConverter.ToInt32(Data); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (Data.Length >= 4) return BitConverter.ToInt32(Data); | |
if (Data.Length >= 4) return BinaryPrimitives.ReadInt32LittleEndian(Data); |
public static implicit operator ContractOrGroup(UInt160 hash) | ||
{ | ||
return new ContractOrGroup() { _data = hash.ToArray() }; | ||
} | ||
|
||
public static implicit operator ContractOrGroup(ECPoint point) | ||
{ | ||
return new ContractOrGroup() { _data = point.EncodePoint(true) }; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
public static implicit operator ContractOrGroup(UInt160 hash) | |
{ | |
return new ContractOrGroup() { _data = hash.ToArray() }; | |
} | |
public static implicit operator ContractOrGroup(ECPoint point) | |
{ | |
return new ContractOrGroup() { _data = point.EncodePoint(true) }; | |
} | |
public static implicit operator ContractOrGroup(UInt160 hash) | |
{ | |
if (hash is null) throw new ArgumentNullException(); | |
return new ContractOrGroup() { _data = hash.ToArray() }; | |
} | |
public static implicit operator ContractOrGroup(ECPoint point) | |
{ | |
if (point is null) throw new ArgumentNullException(); | |
return new ContractOrGroup() { _data = point.EncodePoint(true) }; | |
} |
if (ReferenceEquals(obj, this)) return true; | ||
if (obj is ContractOrGroup other) | ||
{ | ||
if (Data == null || other.Data == null) return Data == other.Data; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (Data == null || other.Data == null) return Data == other.Data; | |
if (Data is null || other is null || other.Data is null) | |
return (Data is null && (other is null || other.Data is null)); |
if (Scopes.HasFlag(WitnessScope.CustomCallingGroups)) | ||
json["allowedcallinggroups"] = AllowedCallingGroup.Select(p => | ||
{ | ||
var obj = new JObject(); | ||
if (p.Key.Data?.Length == 20) | ||
{ | ||
obj["contract"] = p.Key.ToHashString(); | ||
} | ||
else | ||
{ | ||
obj["group"] = p.Key.ToString(); | ||
} | ||
obj["trusts"] = p.Value.Select(v => (JObject)v.ToHashString()).ToArray(); | ||
return obj; | ||
}).ToArray(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (Scopes.HasFlag(WitnessScope.CustomCallingGroups)) | |
json["allowedcallinggroups"] = AllowedCallingGroup.Select(p => | |
{ | |
var obj = new JObject(); | |
if (p.Key.Data?.Length == 20) | |
{ | |
obj["contract"] = p.Key.ToHashString(); | |
} | |
else | |
{ | |
obj["group"] = p.Key.ToString(); | |
} | |
obj["trusts"] = p.Value.Select(v => (JObject)v.ToHashString()).ToArray(); | |
return obj; | |
}).ToArray(); | |
if (Scopes.HasFlag(WitnessScope.CustomCallingGroups)) | |
{ | |
json["allowedcallinggroups"] = AllowedCallingGroup.Select(p => | |
{ | |
var obj = new JObject(); | |
if (p.Key.Data?.Length == 20) | |
{ | |
obj["contract"] = p.Key.ToHashString(); | |
} | |
else | |
{ | |
obj["group"] = p.Key.ToString(); | |
} | |
obj["trusts"] = p.Value.Select(v => (JObject)v.ToHashString()).ToArray(); | |
return obj; | |
}).ToArray(); | |
} | |
if (Scopes.HasFlag(WitnessScope.CustomCallingContracts)) | ||
json["allowedcallingcontracts"] = AllowedCallingContracts.Select(p => | ||
{ | ||
var obj = new JObject(); | ||
obj["contract"] = p.Key.ToString(); | ||
obj["trusts"] = p.Value.Select(v => (JObject)v.ToString()).ToArray(); | ||
return obj; | ||
}).ToArray(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (Scopes.HasFlag(WitnessScope.CustomCallingContracts)) | |
json["allowedcallingcontracts"] = AllowedCallingContracts.Select(p => | |
{ | |
var obj = new JObject(); | |
obj["contract"] = p.Key.ToString(); | |
obj["trusts"] = p.Value.Select(v => (JObject)v.ToString()).ToArray(); | |
return obj; | |
}).ToArray(); | |
if (Scopes.HasFlag(WitnessScope.CustomCallingContracts)) | |
{ | |
json["allowedcallingcontracts"] = AllowedCallingContracts.Select(p => | |
{ | |
var obj = new JObject(); | |
obj["contract"] = p.Key.ToString(); | |
obj["trusts"] = p.Value.Select(v => (JObject)v.ToString()).ToArray(); | |
return obj; | |
}).ToArray(); | |
} |
/// <summary> | ||
/// Custom calling contract hash for contract-specific. | ||
/// </summary> | ||
CustomCallingContracts = 0x02, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need this type now? It seems to me that current CustomCallingGroups
allows to express the same setting easily.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure it should be removed. In [contract]=>[contract] case, it will have more perfromance than CustomCallingGroups
. In my opinion, this is the first choice in most cases.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should keep it and change the key of AllowedCallingGroup
to ECPoint
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should I change AllowedCallingContracts
to [contract]=>[contract or ecpoint], and AllowedCallingGroup
to [ecpoint]=>[contract or ecpoint]?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. I think it is better.
src/neo/IO/Helper.cs
Outdated
for (uint i = 0; i < count; i++) | ||
{ | ||
var key = reader.ReadSerializable<TKey>(); | ||
var value = reader.ReadSerializableArray<TValue>(max); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
max
is used for different things, in fact 16 here is too small, isn't it?
src/neo/IO/Helper.cs
Outdated
/// <typeparam name="TKey"></typeparam> | ||
/// <typeparam name="TValue"></typeparam> | ||
/// <param name="dict"></param> | ||
/// <returns></returns> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fill descriptions please
|
||
public string ToHashString() | ||
{ | ||
if (Data == null) return null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Better to use _data inside the class?
@Ashuaidehao @shargon @Liaojinghui @roman-khimov @superboyiii Please check #2622 |
…to add-new-scope # Conflicts: # src/neo/Network/P2P/Payloads/ContractOrGroup.cs
Close #2583