Skip to content
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

Closed
wants to merge 26 commits into from
Closed

Conversation

Ashuaidehao
Copy link
Contributor

Close #2583

@Ashuaidehao
Copy link
Contributor Author

Ashuaidehao commented Aug 31, 2021

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:[Caller1,Caller2,TokenB,...],
	TokenB:[Caller1,Caller3,...],
	...
}

TokenA,TokenB would call CheckWitness.
Entry=>TokenA, Entry=>Caller1=>TokenA are ok.
Entry=>Caller3=>TokenA will fail.

CustomCallingGroups set contract with trusted caller's group list. Only Trust Group Caller or entry call can pass "CheckWitness".

{
	TokenA:[Group1,Group2,...],
	TokenB:[Group1,Group3,...],
	...
}

Copy link
Contributor

@roman-khimov roman-khimov left a 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.

src/neo/Network/P2P/Payloads/Signer.cs Outdated Show resolved Hide resolved
src/neo/Network/P2P/Payloads/Signer.cs Outdated Show resolved Hide resolved
@Ashuaidehao
Copy link
Contributor Author

For Entry=>Caller1=>Caller2=>..=>CallerN=>TokenA, each of Caller1~CallerN should call CheckWitness to make CustomCallingContracts work. If CallerN won't, it will be the weakness in our trust chain.
CustomCallingGroups is just CustomCallingContracts's group implementation now.
If the group scope is:

{
    NeoFS1:[FSGroup],
    NeoFS2:[FSGroup],
    NeoFS3:[FSGroup],
    NeoFS4:[FSGroup],
}

Entry -> NeoFS1 -> NeoFS2 -> NeoFS3 -> NeoFS4 would work, but Entry -> NeoFS1 -> non-NeoFS won't because non-NeoFS doesn't appear in scope Keys.

@roman-khimov
Copy link
Contributor

If the group scope is:

{
NeoFS1:[FSGroup],
NeoFS2:[FSGroup],
NeoFS3:[FSGroup],
NeoFS4:[FSGroup],
}

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?

@Ashuaidehao
Copy link
Contributor Author

If the group scope is:
{
NeoFS1:[FSGroup],
NeoFS2:[FSGroup],
NeoFS3:[FSGroup],
NeoFS4:[FSGroup],
}

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?

@roman-khimov
Copy link
Contributor

But I can hardly find a way to make simplify and security at the same time now

CountedGroups solve this.

shargon and others added 5 commits September 1, 2021 10:06
…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
@Ashuaidehao
Copy link
Contributor Author

Ashuaidehao commented Sep 1, 2021

I have refacted the CustomCallingGroups implementation, it can support these input format now(Group Public Key should be converted to AddressHash):

{
    Contract:[Contract, Group],
    Group:[Contract, Group],
}

For Entry -> NeoFS1 -> NeoFS2 -> NeoFS3 -> NeoFS4, just use { FSGroup:[] }.

@roman-khimov
Copy link
Contributor

roman-khimov commented Sep 1, 2021

Whoa. Now that's not trivial to explain to people albeit it probably works. Makes me wonder do we need CustomCallingContracts though, looks like CustomCallingGroups covers it completely as well, so it can probably be renamed to CustomCallingEntities or something like that.

Some obligatory nitpicking:

  • we're usually identifying groups by keys, standard script hash may be a bit unexpected here
  • related to previous, calling Contract.CreateSignatureRedeemScript in CheckWitness seems to be somewhat expensive

Maybe we can solve it by allowing both keys and script hashes to be specified (and switching mode accordingly)?

@Ashuaidehao
Copy link
Contributor Author

Whoa. Now that's not trivial to explain to people albeit it probably works. Makes me wonder do we need CustomCallingContracts though, looks like CustomCallingGroups covers it completely as well, so it can probably be renamed to CustomCallingEntities or something like that.

Some obligatory nitpicking:

  • we're usually identifying groups by keys, standard script hash may be a bit unexpected here
  • related to previous, calling Contract.CreateSignatureRedeemScript in CheckWitness seems to be somewhat expensive

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?

@roman-khimov
Copy link
Contributor

I mean we technically can have a map[key_or_hash][keys_or_hashes] or Dictionary<uint160_or_key, uint160_or_key[]>, keys and hashes have different length so it's easy to separate them during decoding phase (if they're VarBytes-encoded). Then you can check for group/hash according to what's inside.

Anyway, CustomCallingContracts becomes a little obsolete with this new CustomCallingGroups.

src/neo/Network/P2P/Payloads/Signer.cs Outdated Show resolved Hide resolved
src/neo/IO/Helper.cs Outdated Show resolved Hide resolved
@Ashuaidehao
Copy link
Contributor Author

phase

Do you mean use byte[] instead of UInt160 and ECPoint(Dictionary<byte[],byte[][]>)? Or add a new custom type combined UInt160 and ECPoint?

/// <summary>
/// Reads an <see cref="ISerializable"/> Dictionary(TKey,TValue[]) from a <see cref="BinaryReader"/>.
/// </summary>
/// <typeparam name="TKey"></typeparam>
Copy link
Member

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;
Copy link
Member

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?

@roman-khimov
Copy link
Contributor

phase

Do you mean use byte[] instead of UInt160 and ECPoint(Dictionary<byte[],byte[][]>)? Or add a new custom type combined UInt160 and ECPoint?

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.

@superboyiii superboyiii mentioned this pull request Sep 9, 2021
9 tasks
public override int GetHashCode()
{
if (Data == null) return 0;
if (Data.Length >= 4) return BitConverter.ToInt32(Data);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (Data.Length >= 4) return BitConverter.ToInt32(Data);
if (Data.Length >= 4) return BinaryPrimitives.ReadInt32LittleEndian(Data);

Comment on lines 38 to 46
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) };
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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));

Comment on lines 119 to 133
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();
Copy link
Contributor

@Jim8y Jim8y Oct 18, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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();
}

Comment on lines 111 to 118
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();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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,
Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Member

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.

Copy link
Contributor Author

@Ashuaidehao Ashuaidehao Nov 1, 2021

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]?

Copy link
Member

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.

@superboyiii superboyiii mentioned this pull request Nov 3, 2021
13 tasks
src/neo/IO/Helper.cs Outdated Show resolved Hide resolved
for (uint i = 0; i < count; i++)
{
var key = reader.ReadSerializable<TKey>();
var value = reader.ReadSerializableArray<TValue>(max);
Copy link
Member

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?

Comment on lines 209 to 212
/// <typeparam name="TKey"></typeparam>
/// <typeparam name="TValue"></typeparam>
/// <param name="dict"></param>
/// <returns></returns>
Copy link
Member

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;
Copy link
Member

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?

@erikzhang
Copy link
Member

@Ashuaidehao @shargon @Liaojinghui @roman-khimov @superboyiii Please check #2622

…to add-new-scope

# Conflicts:
#	src/neo/Network/P2P/Payloads/ContractOrGroup.cs
@erikzhang erikzhang closed this Nov 10, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

CountedGroups and CountedContracts witness scopes
6 participants