private static List <Recipient> GetRecipients(IServiceNodeRegistrationConfig registrationConfig, RegistrationToken registrationToken) { byte[] tokenBytes = registrationToken.GetRegistrationTokenBytes(registrationConfig.PrivateKey); byte[] markerBytes = Encoding.UTF8.GetBytes(RegistrationToken.Marker); var recipients = new List <Recipient> { new Recipient { Amount = registrationConfig.TxOutputValue, ScriptPubKey = TxNullDataTemplate.Instance.GenerateScriptPubKey(markerBytes) } }; recipients.AddRange(BlockChainDataConversions.BytesToPubKeys(tokenBytes).Select(pk => new Recipient { Amount = registrationConfig.TxOutputValue, ScriptPubKey = pk.ScriptPubKey })); if (!recipients.Any()) { throw new Exception("ERROR: No recipients for registration transaction, cannot proceed"); } return(recipients); }
public void ConvertPubKeyTest() { byte[] input = Encoding.ASCII.GetBytes("abc"); List <PubKey> keys = BlockChainDataConversions.BytesToPubKeys(input); byte[] converted = BlockChainDataConversions.PubKeysToBytes(keys); Assert.Equal(0x61, converted[0]); Assert.Equal(0x62, converted[1]); Assert.Equal(0x63, converted[2]); }
public void AddressesToBytes_ShortMessage() { byte[] expectedBytes = new byte[] { 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; var inputAddresses = new List <BitcoinAddress> { BitcoinAddress.Create("TJp6YfcXar2jF1LA3QjmfzfeD26hp9qXNn", RedstoneNetworks.TestNet) }; byte[] output = BlockChainDataConversions.AddressesToBytes(inputAddresses); Assert.Equal(expectedBytes, output); }
public void AddressesToBytes_ShortMessage() { var expectedBytes = new byte[] { 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; List <BitcoinAddress> inputAddresses = new List <BitcoinAddress>(); inputAddresses.Add(BitcoinAddress.Create("mpMqqfKnF9M2rwk9Ai4RymBqADx6TssFuM")); var output = BlockChainDataConversions.AddressesToBytes(inputAddresses); Assert.Equal(expectedBytes, output); }
public Transaction CreateBreezeRegistrationTx(Network network, byte[] data, Money outputValue) { // Funding of the transaction is handled by the 'fundrawtransaction' RPC // call or its equivalent reimplementation. // Only construct the transaction outputs; the change address is handled // automatically by the funding logic // You need to control *where* the change address output appears inside the // transaction to prevent decoding errors with the addresses. Note that if // the fundrawtransaction RPC call is used there is an option that can be // passed to specify the position of the change output (it is randomly // positioned otherwise) Transaction sendTx = new Transaction(); // Recognisable string used to tag the transaction within the blockchain byte[] bytes = Encoding.UTF8.GetBytes("BREEZE_REGISTRATION_MARKER"); sendTx.Outputs.Add(new TxOut() { Value = outputValue, ScriptPubKey = TxNullDataTemplate.Instance.GenerateScriptPubKey(bytes) }); // Add each data-encoding PubKey as a TxOut foreach (PubKey pubKey in BlockChainDataConversions.BytesToPubKeys(data)) { TxOut destTxOut = new TxOut() { Value = outputValue, ScriptPubKey = pubKey.ScriptPubKey }; sendTx.Outputs.Add(destTxOut); } if (sendTx.Outputs.Count == 0) { throw new Exception("ERROR: No outputs in registration transaction, cannot proceed"); } return(sendTx); }
public void BytesToAddresses_ShortMessage() { // a - TJp6YfcXar2jF1LA3QjmfzfeD26hp9qXNn <- correct version on redstone testnet string inputMessage = "a"; byte[] inputMessageBytes = Encoding.ASCII.GetBytes(inputMessage); List <BitcoinAddress> output = BlockChainDataConversions.BytesToAddresses(RedstoneNetworks.TestNet, inputMessageBytes); var expectedOutput = new List <BitcoinAddress> { BitcoinAddress.Create("TJp6YfcXar2jF1LA3QjmfzfeD26hp9qXNn") }; /* Worked example * // 0x00 - Bitcoin Mainnet * // 0x6F - Bitcoin Testnet * // 0x41 - Redstone test <---- * // 0x3c - Redstone main * * // Literal 'a' is 0x61 hex * var keyBytes = new byte[] {0x41, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; * var algorithm = SHA256.Create(); * var hash = algorithm.ComputeHash(algorithm.ComputeHash(keyBytes)); * * //First 4 bytes of double SHA256: 15, 146, 165, 196 * //Need to concatenate them to keyBytes * * var keyBytes2 = new byte[] {0x41, * 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, * 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, * 134, 212, 83, 7}; * * var finalEncodedAddress = Encoders.Base58.EncodeData(keyBytes2); * /* * Result should be "TJp6YfcXar2jF1LA3QjmfzfeD26hp9qXNn" */ Assert.Equal(expectedOutput, output); }
public void BytesToAddresses_ShortMessage() { // a - mpMqqfKnF9M2rwk9Ai4RymBqADx6TssFuM <- correct version on testnet // mpMqqfKnF9M2rwk9Ai4RymBqADx6TUnBkb <- incorrect version with 00000000 checksum bytes string inputMessage = "a"; byte[] inputMessageBytes = Encoding.ASCII.GetBytes(inputMessage); List <BitcoinAddress> output = BlockChainDataConversions.BytesToAddresses(Network.TestNet, inputMessageBytes); List <BitcoinAddress> expectedOutput = new List <BitcoinAddress>(); expectedOutput.Add(BitcoinAddress.Create("mpMqqfKnF9M2rwk9Ai4RymBqADx6TssFuM")); /* Worked example * // 0x00 - Mainnet * // 0x6F - Testnet * // 0x?? - Stratis mainnet * * // Literal 'a' is 0x61 hex * * var keyBytes = new byte[] {0x6F, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; * var algorithm = SHA256.Create(); * var hash = algorithm.ComputeHash(algorithm.ComputeHash(keyBytes)); * * First 4 bytes of double SHA256: 15, 146, 165, 196 * Need to concatenate them to keyBytes * * var keyBytes2 = new byte[] {0x6F, * 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, * 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, * 15, 146, 165, 196}; * * var finalEncodedAddress = Encoders.Base58.EncodeData(keyBytes2); * * Result should be "mpMqqfKnF9M2rwk9Ai4RymBqADx6TssFuM" */ Assert.Equal(expectedOutput, output); }
public async Task DummyRegistration(string originWalletName, string originWalletPassword) { // TODO: Move this functionality into the tests var token = new List <byte>(); // Server ID token.AddRange(Encoding.ASCII.GetBytes("".PadRight(34))); // IPv4 address token.Add(0x00); token.Add(0x00); token.Add(0x00); token.Add(0x00); // IPv6 address token.Add(0x00); token.Add(0x00); token.Add(0x00); token.Add(0x00); token.Add(0x00); token.Add(0x00); token.Add(0x00); token.Add(0x00); token.Add(0x00); token.Add(0x00); token.Add(0x00); token.Add(0x00); token.Add(0x00); token.Add(0x00); token.Add(0x00); token.Add(0x00); // Onion address token.Add(0x00); token.Add(0x00); token.Add(0x00); token.Add(0x00); token.Add(0x00); token.Add(0x00); token.Add(0x00); token.Add(0x00); token.Add(0x00); token.Add(0x00); token.Add(0x00); token.Add(0x00); token.Add(0x00); token.Add(0x00); token.Add(0x00); token.Add(0x00); // Port number byte[] portNumber = BitConverter.GetBytes(37123); token.Add(portNumber[0]); token.Add(portNumber[1]); // RSA sig length byte[] rsaLength = BitConverter.GetBytes(256); token.Add(rsaLength[0]); token.Add(rsaLength[1]); // RSA signature byte[] rsaSig = new byte[256]; token.AddRange(rsaSig); // ECDSA sig length byte[] ecdsaLength = BitConverter.GetBytes(128); token.Add(ecdsaLength[0]); token.Add(ecdsaLength[1]); // ECDSA signature byte[] ecdsaSig = new byte[128]; token.AddRange(ecdsaSig); // Configuration hash token.AddRange(Encoding.ASCII.GetBytes("aa4e984c5655a677716539acc8cbc0ce29331429")); // Finally add protocol byte and computed length to beginning of header byte[] protocolVersionByte = BitConverter.GetBytes(254); byte[] headerLength = BitConverter.GetBytes(token.Count); token.Insert(0, protocolVersionByte[0]); token.Insert(1, headerLength[0]); token.Insert(2, headerLength[1]); Money outputValue = new Money(0.0001m, MoneyUnit.BTC); Transaction sendTx = new Transaction(); // Recognisable string used to tag the transaction within the blockchain byte[] bytes = Encoding.UTF8.GetBytes("BREEZE_REGISTRATION_MARKER"); sendTx.Outputs.Add(new TxOut() { Value = outputValue, ScriptPubKey = TxNullDataTemplate.Instance.GenerateScriptPubKey(bytes) }); // Add each data-encoding PubKey as a TxOut foreach (PubKey pubKey in BlockChainDataConversions.BytesToPubKeys(token.ToArray())) { TxOut destTxOut = new TxOut() { Value = outputValue, ScriptPubKey = pubKey.ScriptPubKey }; sendTx.Outputs.Add(destTxOut); } HdAccount highestAcc = null; foreach (HdAccount account in this.walletManager.GetAccounts(originWalletName)) { if (highestAcc == null) { highestAcc = account; } if (account.GetSpendableAmount().ConfirmedAmount > highestAcc.GetSpendableAmount().ConfirmedAmount) { highestAcc = account; } } // This fee rate is primarily for regtest, testnet and mainnet have actual estimators that work FeeRate feeRate = new FeeRate(new Money(10000, MoneyUnit.Satoshi)); WalletAccountReference accountRef = new WalletAccountReference(originWalletName, highestAcc.Name); List <Recipient> recipients = new List <Recipient>(); TransactionBuildContext txBuildContext = new TransactionBuildContext(accountRef, recipients); txBuildContext.WalletPassword = originWalletPassword; txBuildContext.OverrideFeeRate = feeRate; txBuildContext.Sign = true; txBuildContext.MinConfirmations = 0; this.walletTransactionHandler.FundTransaction(txBuildContext, sendTx); this.logger.LogDebug("Trying to broadcast transaction: " + sendTx.GetHash()); await this.broadcasterManager.BroadcastTransactionAsync(sendTx).ConfigureAwait(false); var bcResult = this.broadcasterManager.GetTransaction(sendTx.GetHash()).State; switch (bcResult) { case Stratis.Bitcoin.Broadcasting.State.Broadcasted: case Stratis.Bitcoin.Broadcasting.State.Propagated: this.logger.LogDebug("Broadcasted transaction: " + sendTx.GetHash()); break; case Stratis.Bitcoin.Broadcasting.State.ToBroadcast: // Wait for propagation var waited = TimeSpan.Zero; var period = TimeSpan.FromSeconds(1); while (TimeSpan.FromSeconds(21) > waited) { // Check BroadcasterManager for broadcast success var transactionEntry = this.broadcasterManager.GetTransaction(sendTx.GetHash()); if (transactionEntry != null && transactionEntry.State == Stratis.Bitcoin.Broadcasting.State.Propagated) { // TODO: This is cluttering up the console, only need to log it once this.logger.LogDebug("Propagated transaction: " + sendTx.GetHash()); } await Task.Delay(period).ConfigureAwait(false); waited += period; } break; case Stratis.Bitcoin.Broadcasting.State.CantBroadcast: // Do nothing break; } this.logger.LogDebug("Uncertain if transaction was propagated: " + sendTx.GetHash()); }
public void ParseTransaction(Transaction tx, Network network) { var markerIndex = GetMarkerIndex(tx); if (markerIndex == -1) { throw new Exception("Missing Redstone registration marker from first transaction output"); } // Peek at first non-nulldata address to get the length information, // this indicates how many outputs have been used for encoding, and // by extension indicates if there will be a change address output PubKey[] tempPubKeyArray = tx.Outputs[markerIndex + 1].ScriptPubKey.GetDestinationPublicKeys(network); if (tempPubKeyArray.Length > 1) { // This can't have been generated by a server registration, we don't use // multiple signatures for the registration transaction outputs by design throw new Exception("Registration transaction output has too many PubKeys"); } byte[] secondOutputData = BlockChainDataConversions.PubKeyToBytes(tempPubKeyArray[0]); var protocolVersion = (int)secondOutputData[0]; var headerLength = (secondOutputData[2] << 8) + secondOutputData[1]; // 64 = number of bytes we can store per output int numPubKeys = headerLength / 64; // Is there a partially 'full' PubKey holding the remainder of the bytes? if (headerLength % 64 != 0) { numPubKeys++; } if (tx.Outputs.Count < numPubKeys + 1) { throw new Exception("Too few transaction outputs, registration transaction incomplete"); } PubKey[] tempPK; var pubKeyList = new List <PubKey>(); for (int i = markerIndex + 1; i < numPubKeys + markerIndex + 1; i++) { tempPK = tx.Outputs[i].ScriptPubKey.GetDestinationPublicKeys(network); if (tempPK.Length > 1) { // This can't have been generated by a server registration, we don't use // multiple signatures for the registration transaction outputs by design throw new Exception("Registration transaction output has too many PubKeys"); } pubKeyList.Add(tempPK[0]); } byte[] bitstream = BlockChainDataConversions.PubKeysToBytes(pubKeyList); // Need to consume X bytes at a time off the bitstream and convert them to various // data types, then set member variables to the retrieved values. // Skip over protocol version and header length bytes int position = 3; this.ProtocolVersion = protocolVersion; byte[] collateralPubKeyHashTemp = GetSubArray(bitstream, position, 20); this.CollateralPubKeyHash = new KeyId(collateralPubKeyHashTemp); position += 20; byte[] rewaredPubKeyHashTemp = GetSubArray(bitstream, position, 20); this.RewardPubKeyHash = new KeyId(rewaredPubKeyHashTemp); position += 20; // Either a valid IPv4 address, or all zero bytes bool allZeroes = true; byte[] ipv4temp = GetSubArray(bitstream, position, 4); for (int i = 0; i < ipv4temp.Length; i++) { if (ipv4temp[i] != 0) { allZeroes = false; } } if (!allZeroes) { this.Ipv4Addr = new IPAddress(ipv4temp); } else { this.Ipv4Addr = IPAddress.None; } position += 4; // Either a valid IPv6 address, or all zero bytes allZeroes = true; byte[] ipv6temp = GetSubArray(bitstream, position, 16); for (int i = 0; i < ipv6temp.Length; i++) { if (ipv6temp[i] != 0) { allZeroes = false; } } if (!allZeroes) { this.Ipv6Addr = new IPAddress(ipv6temp); } else { this.Ipv6Addr = IPAddress.IPv6None; } position += 16; // Either a valid onion address, or all zero bytes allZeroes = true; byte[] onionTemp = GetSubArray(bitstream, position, 16); for (int i = 0; i < onionTemp.Length; i++) { if (onionTemp[i] != 0) { allZeroes = false; } } if (!allZeroes) { this.OnionAddress = Encoding.ASCII.GetString(onionTemp); } else { this.OnionAddress = null; } position += 16; var temp = GetSubArray(bitstream, position, 2); this.Port = (temp[1] << 8) + temp[0]; position += 2; temp = GetSubArray(bitstream, position, 2); var servicEndpointLength = (temp[1] << 8) + temp[0]; position += 4; var serviceEndpointTemp = GetSubArray(bitstream, position, servicEndpointLength); this.ServiceEndpoint = new Uri(Encoding.ASCII.GetString(serviceEndpointTemp)); position += servicEndpointLength; temp = GetSubArray(bitstream, position, 2); var ecdsaLength = (temp[1] << 8) + temp[0]; position += 4; this.Signature = GetSubArray(bitstream, position, ecdsaLength); position += ecdsaLength; temp = GetSubArray(bitstream, position, 2); var ecdsaPubKeyLength = (temp[1] << 8) + temp[0]; position += 2; this.PubKey = new PubKey(GetSubArray(bitstream, position, ecdsaPubKeyLength)); position += ecdsaPubKeyLength; // TODO: Validate signatures }
public void RegistrationTest() { using (NodeBuilder builder = NodeBuilder.Create()) { CoreNode node1 = builder.CreateStratisPosNode(true, fullNodeBuilder => { fullNodeBuilder .UseStratisConsensus() .UseBlockStore() .UseMempool() .UseBlockNotification() .UseTransactionNotification() .UseWallet() .UseWatchOnlyWallet() .AddPowPosMining() //.AddMining() //.UseApi() .AddRPC(); }); CoreNode node2 = builder.CreateStratisPosNode(true, fullNodeBuilder => { fullNodeBuilder .UseStratisConsensus() .UseBlockStore() .UseMempool() .UseBlockNotification() .UseTransactionNotification() .UseWallet() .UseWatchOnlyWallet() .AddPowPosMining() //.AddMining() //.UseApi() .AddRPC() .UseRegistration(); }); node1.NotInIBD(); node2.NotInIBD(); var rpc1 = node1.CreateRPCClient(); var rpc2 = node2.CreateRPCClient(); // addnode RPC call does not seem to work, so connect directly node1.FullNode.ConnectionManager.AddNodeAddress(node2.Endpoint); // Create the originating node's wallet var wm1 = node1.FullNode.NodeService <IWalletManager>() as WalletManager; wm1.CreateWallet("Registration1", "registration"); var wallet1 = wm1.GetWalletByName("registration"); var account1 = wallet1.GetAccountsByCoinType((CoinType)node1.FullNode.Network.Consensus.CoinType).First(); var address1 = account1.GetFirstUnusedReceivingAddress(); var secret1 = wallet1.GetExtendedPrivateKeyForAddress("Registration1", address1); node1.SetDummyMinerSecret(new BitcoinSecret(secret1.PrivateKey, node1.FullNode.Network)); // Generate a block so we have some funds to create a transaction with node1.GenerateStratisWithMiner(52); TestHelper.TriggerSync(node1); TestHelper.TriggerSync(node2); TestHelper.WaitLoop(() => rpc1.GetBestBlockHash() == rpc2.GetBestBlockHash()); var rsa = new RsaKey(); var ecdsa = new Key().GetBitcoinSecret(Network.RegTest); var serverAddress = ecdsa.GetAddress().ToString(); var token = new RegistrationToken(1, serverAddress, IPAddress.Parse("127.0.0.1"), IPAddress.Parse("2001:0db8:85a3:0000:0000:8a2e:0370:7334"), "0123456789ABCDEF", "", 37123, ecdsa.PubKey); var cryptoUtils = new CryptoUtils(rsa, ecdsa); token.RsaSignature = cryptoUtils.SignDataRSA(token.GetHeaderBytes().ToArray()); token.EcdsaSignature = cryptoUtils.SignDataECDSA(token.GetHeaderBytes().ToArray()); byte[] msgBytes = token.GetRegistrationTokenBytes(rsa, ecdsa); Transaction sendTx = new Transaction(); Money outputValue = new Money(0.01m, MoneyUnit.BTC); Money feeValue = new Money(0.01m, MoneyUnit.BTC); byte[] bytes = Encoding.UTF8.GetBytes("BREEZE_REGISTRATION_MARKER"); sendTx.Outputs.Add(new TxOut() { Value = outputValue, ScriptPubKey = TxNullDataTemplate.Instance.GenerateScriptPubKey(bytes) }); foreach (PubKey pubKey in BlockChainDataConversions.BytesToPubKeys(msgBytes)) { TxOut destTxOut = new TxOut() { Value = outputValue, ScriptPubKey = pubKey.ScriptPubKey }; sendTx.Outputs.Add(destTxOut); } var wth1 = node1.FullNode.NodeService <IWalletTransactionHandler>() as WalletTransactionHandler; List <Recipient> recipients = new List <Recipient>(); foreach (TxOut txOut in sendTx.Outputs) { recipients.Add(new Recipient() { Amount = txOut.Value, ScriptPubKey = txOut.ScriptPubKey }); } var walletReference = new WalletAccountReference() { // Default to the first wallet & first account AccountName = wm1.Wallets.First().GetAccountsByCoinType((CoinType)node1.FullNode.Network.Consensus.CoinType).First().Name, WalletName = wm1.Wallets.First().Name }; var context = new TransactionBuildContext( walletReference, recipients, "Registration1") { MinConfirmations = 0, OverrideFeeRate = new FeeRate(new Money(0.001m, MoneyUnit.BTC)), Shuffle = false, Sign = true }; var tx = wth1.BuildTransaction(context); node1.AddToStratisPosMempool(tx); TestHelper.WaitLoop(() => rpc1.GetRawMempool().Length > 0); node1.GenerateStratisWithMiner(1); Thread.Sleep(10000); TestHelper.WaitLoop(() => rpc1.GetBestBlockHash() == rpc2.GetBestBlockHash()); Console.WriteLine("Checking if registration was received..."); var rm2 = node2.FullNode.NodeService <RegistrationManager>(); var rs2 = rm2.GetRegistrationStore(); foreach (var record in rs2.GetAll()) { Console.WriteLine("Received registration: " + record.RecordTxId); } Console.WriteLine(rs2.GetAll().Count); Thread.Sleep(10000); node1.Kill(); node2.Kill(); } }