private static TrustlessKeygen TestSerializationRoundTrip(TrustlessKeygen keyGen, EcdsaKeyPair keyPair) { var bytes = keyGen.ToBytes(); var restored = TrustlessKeygen.FromBytes(bytes, keyPair); Assert.AreEqual(restored, keyGen); return(restored); }
public void Test_OneNodeCycle() { var stateManager = _container?.Resolve <IStateManager>(); var contractRegisterer = _container?.Resolve <IContractRegisterer>(); var tx = new TransactionReceipt(); var sender = new BigInteger(0).ToUInt160(); var context = new InvocationContext(sender, stateManager !.LastApprovedSnapshot, tx); var contract = new GovernanceContract(context); var keyPair = new EcdsaKeyPair("0xD95D6DB65F3E2223703C5D8E205D98E3E6B470F067B0F94F6C6BF73D4301CE48" .HexToBytes().ToPrivateKey()); byte[] pubKey = CryptoUtils.EncodeCompressed(keyPair.PublicKey); ECDSAPublicKey[] allKeys = { keyPair.PublicKey }; var keygen = new TrustlessKeygen(keyPair, allKeys, 0, 0); var cycle = 0.ToUInt256(); ValueMessage value; // call ChangeValidators method { byte[][] validators = { pubKey }; var input = ContractEncoder.Encode(GovernanceInterface.MethodChangeValidators, cycle, validators); var call = contractRegisterer !.DecodeContract(context, ContractRegisterer.GovernanceContract, input); Assert.IsNotNull(call); var frame = new SystemContractExecutionFrame(call !, context, input, 100_000_000); Assert.AreEqual(ExecutionStatus.Ok, contract.ChangeValidators(cycle, validators, frame)); } // check correct validator { var input = ContractEncoder.Encode(GovernanceInterface.MethodIsNextValidator, pubKey); var call = contractRegisterer.DecodeContract(context, ContractRegisterer.GovernanceContract, input); Assert.IsNotNull(call); var frame = new SystemContractExecutionFrame(call !, context, input, 100_000_000); Assert.AreEqual(ExecutionStatus.Ok, contract.IsNextValidator(pubKey, frame)); Assert.AreEqual(frame.ReturnValue, 1.ToUInt256().ToBytes()); } // check incorrect validator { byte[] incorrectPubKey = pubKey.Reverse().ToArray(); var input = ContractEncoder.Encode(GovernanceInterface.MethodIsNextValidator, incorrectPubKey); var call = contractRegisterer.DecodeContract(context, ContractRegisterer.GovernanceContract, input); Assert.IsNotNull(call); var frame = new SystemContractExecutionFrame(call !, context, input, 100_000_000); Assert.AreEqual(ExecutionStatus.Ok, contract.IsNextValidator(incorrectPubKey, frame)); Assert.AreEqual(frame.ReturnValue, 0.ToUInt256().ToBytes()); } // call commit { var commitMessage = keygen.StartKeygen(); byte[] commitment = commitMessage.Commitment.ToBytes(); byte[][] encryptedRows = commitMessage.EncryptedRows; var input = ContractEncoder.Encode(GovernanceInterface.MethodKeygenCommit, cycle, commitment, encryptedRows); var call = contractRegisterer.DecodeContract(context, ContractRegisterer.GovernanceContract, input); Assert.IsNotNull(call); var frame = new SystemContractExecutionFrame(call !, context, input, 100_000_000); Assert.AreEqual(ExecutionStatus.Ok, contract.KeyGenCommit(cycle, commitment, encryptedRows, frame)); // several calls is ok Assert.AreEqual(ExecutionStatus.Ok, contract.KeyGenCommit(cycle, commitment, encryptedRows, frame)); // set keygen state value = keygen.HandleCommit(0, commitMessage); } // send value { var proposer = new BigInteger(0).ToUInt256(); var input = ContractEncoder.Encode(GovernanceInterface.MethodKeygenSendValue, cycle, proposer, value.EncryptedValues); var call = contractRegisterer.DecodeContract(context, ContractRegisterer.GovernanceContract, input); Assert.IsNotNull(call); var frame = new SystemContractExecutionFrame(call !, context, input, 100_000_000); Assert.AreEqual(ExecutionStatus.Ok, contract.KeyGenSendValue(cycle, proposer, value.EncryptedValues, frame)); // set keygen state Assert.IsTrue(keygen.HandleSendValue(0, value)); Assert.IsTrue(keygen.Finished()); } // confirm { ThresholdKeyring?keyring = keygen.TryGetKeys(); Assert.IsNotNull(keyring); var input = ContractEncoder.Encode(GovernanceInterface.MethodKeygenConfirm, cycle, keyring !.Value.TpkePublicKey.ToBytes(), keyring !.Value.ThresholdSignaturePublicKeySet.Keys.Select(key => key.ToBytes()).ToArray()); var call = contractRegisterer.DecodeContract(context, ContractRegisterer.GovernanceContract, input); Assert.IsNotNull(call); var frame = new SystemContractExecutionFrame(call !, context, input, 100_000_000); Assert.AreEqual(ExecutionStatus.Ok, contract.KeyGenConfirm(cycle, keyring !.Value.TpkePublicKey.ToBytes(), keyring !.Value.ThresholdSignaturePublicKeySet.Keys.Select(key => key.ToBytes()).ToArray(), frame)); // set keygen state Assert.IsTrue(keygen.HandleConfirm(keyring !.Value.TpkePublicKey, keyring !.Value.ThresholdSignaturePublicKeySet)); } // check no validators in storage Assert.Throws <ConsensusStateNotPresentException>(() => context.Snapshot.Validators.GetValidatorsPublicKeys()); // finish cycle { var input = ContractEncoder.Encode(GovernanceInterface.MethodFinishCycle, cycle); var call = contractRegisterer.DecodeContract(context, ContractRegisterer.GovernanceContract, input); Assert.IsNotNull(call); var frame = new SystemContractExecutionFrame(call !, context, input, 100_000_000); // should fail due to the invalid block Assert.AreEqual(ExecutionStatus.ExecutionHalted, contract.FinishCycle(cycle, frame)); // set next cycle block number in frame: frame.InvocationContext.Receipt.Block = StakingContract.CycleDuration; Assert.AreEqual(ExecutionStatus.Ok, contract.FinishCycle(cycle, frame)); } // check new validators in storage var newValidators = context.Snapshot.Validators.GetValidatorsPublicKeys().ToArray(); Assert.AreEqual(newValidators.Count(), 1); Assert.AreEqual(newValidators[0], keyPair.PublicKey); }
public void Test_InvalidValidatorKey() { var stateManager = _container?.Resolve <IStateManager>(); var contractRegisterer = _container?.Resolve <IContractRegisterer>(); var tx = new TransactionReceipt(); var sender = new BigInteger(0).ToUInt160(); var context = new InvocationContext(sender, stateManager !.LastApprovedSnapshot, tx); var contract = new GovernanceContract(context); var keyPair = new EcdsaKeyPair(Crypto.GeneratePrivateKey().ToPrivateKey()); ECDSAPublicKey[] allKeys = { keyPair.PublicKey }; var keygen = new TrustlessKeygen(keyPair, allKeys, 0, 0); var cycle = 0.ToUInt256(); ValueMessage value; // call ChangeValidators method with invalid key { byte[][] validators = { new byte[] { 0 } }; var input = ContractEncoder.Encode(GovernanceInterface.MethodChangeValidators, cycle, validators); var call = contractRegisterer !.DecodeContract(context, ContractRegisterer.GovernanceContract, input); Assert.IsNotNull(call); var frame = new SystemContractExecutionFrame(call !, context, input, 100_000_000); Assert.AreEqual(ExecutionStatus.ExecutionHalted, contract.ChangeValidators(cycle, validators, frame)); } // call commit { var commitMessage = keygen.StartKeygen(); byte[] commitment = commitMessage.Commitment.ToBytes(); byte[][] encryptedRows = commitMessage.EncryptedRows; var input = ContractEncoder.Encode(GovernanceInterface.MethodKeygenCommit, cycle, commitment, encryptedRows); var call = contractRegisterer.DecodeContract(context, ContractRegisterer.GovernanceContract, input); Assert.IsNotNull(call); var frame = new SystemContractExecutionFrame(call !, context, input, 100_000_000); Assert.AreEqual(ExecutionStatus.ExecutionHalted, contract.KeyGenCommit(cycle, commitment, encryptedRows, frame)); // set keygen state value = keygen.HandleCommit(0, commitMessage); } // send value { var proposer = new BigInteger(0).ToUInt256(); var input = ContractEncoder.Encode(GovernanceInterface.MethodKeygenSendValue, cycle, proposer, value.EncryptedValues); var call = contractRegisterer.DecodeContract(context, ContractRegisterer.GovernanceContract, input); Assert.IsNotNull(call); var frame = new SystemContractExecutionFrame(call !, context, input, 100_000_000); Assert.AreEqual(ExecutionStatus.ExecutionHalted, contract.KeyGenSendValue(cycle, proposer, value.EncryptedValues, frame)); // set keygen state Assert.IsTrue(keygen.HandleSendValue(0, value)); Assert.IsTrue(keygen.Finished()); } // confirm { ThresholdKeyring?keyring = keygen.TryGetKeys(); Assert.IsNotNull(keyring); var input = ContractEncoder.Encode(GovernanceInterface.MethodKeygenConfirm, cycle, keyring !.Value.TpkePublicKey.ToBytes(), keyring !.Value.ThresholdSignaturePublicKeySet.Keys.Select(key => key.ToBytes()).ToArray()); var call = contractRegisterer.DecodeContract(context, ContractRegisterer.GovernanceContract, input); Assert.IsNotNull(call); var frame = new SystemContractExecutionFrame(call !, context, input, 100_000_000); Assert.AreEqual(ExecutionStatus.Ok, contract.KeyGenConfirm(cycle, keyring !.Value.TpkePublicKey.ToBytes(), keyring !.Value.ThresholdSignaturePublicKeySet.Keys.Select(key => key.ToBytes()).ToArray(), frame)); // set keygen state Assert.IsTrue(keygen.HandleConfirm(keyring !.Value.TpkePublicKey, keyring !.Value.ThresholdSignaturePublicKeySet)); } // check no validators in storage Assert.Throws <ConsensusStateNotPresentException>(() => context.Snapshot.Validators.GetValidatorsPublicKeys()); // finish cycle { var input = ContractEncoder.Encode(GovernanceInterface.MethodFinishCycle, cycle); var call = contractRegisterer.DecodeContract(context, ContractRegisterer.GovernanceContract, input); Assert.IsNotNull(call); var frame = new SystemContractExecutionFrame(call !, context, input, 100_000_000); // set next cycle block number in frame: frame.InvocationContext.Receipt.Block = StakingContract.CycleDuration; Assert.AreEqual(ExecutionStatus.Ok, contract.FinishCycle(cycle, frame)); } // check no validators in storage again Assert.IsEmpty(context.Snapshot.Validators.GetValidatorsPublicKeys()); }
// For every cycle, a new set of keys are required for the validators. This key generation process // is done on-chain. That means, every communication between participating nodes happen via transactions // in the block. For example, if node A wants to send a msg to node B, then node A encrypts the // msg with node B's public key and broadcast this as a transaction to the governance contract. // After this transaction is added to the chain, node B can decrypt the msg and read it. // During block execution, after every system transaction is executed, the following method // is invoked. It evaluates the transaction and if it's keygen related, it produces // appropriate response in form of a transaction and adds it to the pool for the addition // in the block. private void BlockManagerOnSystemContractInvoked(object _, InvocationContext context) { if (context.Receipt is null) { return; } var highestBlock = _blockSynchronizer.GetHighestBlock(); var willParticipate = !highestBlock.HasValue || GovernanceContract.IsKeygenBlock(context.Receipt.Block) && GovernanceContract.SameCycle(highestBlock.Value, context.Receipt.Block); if (!willParticipate) { Logger.LogInformation( highestBlock != null ? $"Will not participate in keygen: highest block is {highestBlock.Value}, call block is {context.Receipt.Block}" : $"Will not participate in keygen: highest block is null, call block is {context.Receipt.Block}" ); } var tx = context.Receipt.Transaction; if ( !tx.To.Equals(ContractRegisterer.GovernanceContract) && !tx.To.Equals(ContractRegisterer.StakingContract) ) { return; } if (context.Receipt.Block < _blockManager.GetHeight() && !GovernanceContract.SameCycle(context.Receipt.Block, _blockManager.GetHeight())) { Logger.LogWarning( $"System contract invoked from outdated tx: {context.Receipt.Hash}, tx block {context.Receipt.Block}, our height is {_blockManager.GetHeight()}"); return; } if (tx.Invocation.Length < 4) { return; } var signature = ContractEncoder.MethodSignatureAsInt(tx.Invocation); var decoder = new ContractDecoder(tx.Invocation.ToArray()); var contractAddress = tx.To; if (contractAddress.Equals(ContractRegisterer.GovernanceContract) && signature == ContractEncoder.MethodSignatureAsInt(GovernanceInterface.MethodFinishCycle)) { Logger.LogDebug("Aborting ongoing keygen because cycle was finished"); _keyGenRepository.SaveKeyGenState(Array.Empty <byte>()); } else if (signature == ContractEncoder.MethodSignatureAsInt(StakingInterface.MethodFinishVrfLottery)) { Logger.LogDebug($"Detected call of StakingInterface.{StakingInterface.MethodFinishVrfLottery}"); var cycle = GovernanceContract.GetCycleByBlockNumber(context.Receipt.Block); var data = new GovernanceContract(context).GetNextValidators(); var publicKeys = (data ?? throw new ArgumentException("Cannot parse method args")) .Select(x => x.ToPublicKey()) .ToArray(); Logger.LogDebug( $"Keygen is started in cycle={cycle}, block={context.Receipt.Block} for validator set: {string.Join(",", publicKeys.Select(x => x.ToHex()))}" ); if (!publicKeys.Contains(_privateWallet.EcdsaKeyPair.PublicKey)) { Logger.LogWarning("Skipping validator change event since we are not new validator"); return; } var keygen = GetCurrentKeyGen(); if (keygen != null && keygen.Cycle == cycle) { throw new ArgumentException("Cannot start keygen, since one is already running"); } if (keygen != null) { Logger.LogWarning($"Aborted keygen for cycle {keygen.Cycle} to start keygen for cycle {cycle}"); } _keyGenRepository.SaveKeyGenState(Array.Empty <byte>()); var faulty = (publicKeys.Length - 1) / 3; keygen = new TrustlessKeygen(_privateWallet.EcdsaKeyPair, publicKeys, faulty, cycle); var commitTx = MakeCommitTransaction(keygen.StartKeygen(), cycle); Logger.LogTrace($"Produced commit tx with hash: {commitTx.Hash.ToHex()}"); if (willParticipate) { Logger.LogInformation($"Try to send KeyGen Commit transaction"); if (_transactionPool.Add(commitTx) is var error && error != OperatingError.Ok) { Logger.LogError($"Error creating commit transaction ({commitTx.Hash.ToHex()}): {error}"); } else { Logger.LogInformation($"KeyGen Commit transaction sent"); } } Logger.LogDebug($"Saving keygen {keygen.ToBytes().ToHex()}"); _keyGenRepository.SaveKeyGenState(keygen.ToBytes()); }