示例#1
0
        public static void TryUpdateLookAhead(string passphrase, X1WalletFile x1WalletFile)
        {
            if (string.IsNullOrWhiteSpace(passphrase))
            {
                return;
            }

            x1WalletFile.LookAhead.Clear();

            var decryptedSeed     = VCL.DecryptWithPassphrase(passphrase, x1WalletFile.HdSeed);
            var changeAddressGaps = GetIndexGaps(C.Change, x1WalletFile);

            foreach (var index in changeAddressGaps)
            {
                var generated = GeneratePubKeyHashAddress(decryptedSeed, passphrase, x1WalletFile.CoinType, C.Change, index);
                x1WalletFile.LookAhead[generated.Address] = generated;
            }

            var receiveAddressGaps = GetIndexGaps(C.External, x1WalletFile);

            foreach (var index in receiveAddressGaps)
            {
                var generated = GeneratePubKeyHashAddress(decryptedSeed, passphrase, x1WalletFile.CoinType, C.External, index);
                x1WalletFile.LookAhead[generated.Address] = generated;
            }

            x1WalletFile.SaveX1WalletFile(x1WalletFile.CurrentPath);
        }
示例#2
0
        public static bool IsLabelUnique(string label, X1WalletFile x1WalletFile)
        {
            foreach (var address in x1WalletFile.PubKeyHashAddresses.Values)
            {
                if (address.Label == label)
                {
                    return(false);
                }
            }
            foreach (var address in x1WalletFile.MultiSigAddresses.Values)
            {
                if (address.Label == label)
                {
                    return(false);
                }
            }
            foreach (var address in x1WalletFile.ColdStakingAddresses.Values)
            {
                if (address.Label == label)
                {
                    return(false);
                }
            }

            return(true);
        }
示例#3
0
        public static ISegWitAddress GetOrAddAddress(string bech32, int blockHeight, X1WalletFile x1WalletFile)
        {
            ISegWitAddress existing = null;

            if (x1WalletFile.PubKeyHashAddresses.TryGetValue(bech32, out var pubKeyHashAddress))
            {
                existing = pubKeyHashAddress;
            }
            else if (x1WalletFile.ColdStakingAddresses.TryGetValue(bech32, out var coldStakingAddress))
            {
                existing = coldStakingAddress;
            }
            else if (x1WalletFile.MultiSigAddresses.TryGetValue(bech32, out var multiSigAddress))
            {
                existing = multiSigAddress;
            }
            if (existing != null)
            {
                existing.LastSeenHeight = blockHeight;
                x1WalletFile.SaveX1WalletFile(x1WalletFile.CurrentPath);
                return(existing);
            }

            if (x1WalletFile.LookAhead.TryGetValue(bech32, out var gapAddress))
            {
                gapAddress.LastSeenHeight = blockHeight;
                x1WalletFile.PubKeyHashAddresses[gapAddress.Address] = gapAddress;
                x1WalletFile.LookAhead.TryRemove(gapAddress.Address, out var _);
                x1WalletFile.SaveX1WalletFile(x1WalletFile.CurrentPath);
                return(gapAddress);
            }
            return(null);
        }
示例#4
0
 static IEnumerable <int> GetIndexesInUse(int isChange, X1WalletFile x1WalletFile)
 {
     return(x1WalletFile.PubKeyHashAddresses.Values.Where(x =>
                                                          x.KeyMaterial.KeyType == KeyType.Hd &&
                                                          x.KeyMaterial.IsChange == isChange &&
                                                          x.KeyMaterial.AddressIndex.HasValue).Select(x => x.KeyMaterial.AddressIndex.Value));
 }
示例#5
0
        protected WalletCore(NodeServices nodeServices, string walletPath)
        {
            this.nodeServices = nodeServices;

            this.WalletPath               = walletPath;
            this.x1WalletFile             = WalletHelper.LoadX1WalletFile(walletPath);
            this.x1WalletFile.CurrentPath = walletPath;
            this.WalletName               = this.x1WalletFile.WalletName;
            this.metadataPath             = this.x1WalletFile.WalletName.GetX1WalletMetaDataFilepath(C.Network, nodeServices.DataFolder);
            this.metadata = WalletHelper.LoadOrCreateX1WalletMetadataFile(this.metadataPath, this.x1WalletFile, C.Network.GenesisHash);

            ScheduleSyncing();
        }
示例#6
0
 public static X1WalletMetadataFile CreateX1WalletMetadataFile(this X1WalletFile x1WalletFile,
                                                               uint256 genesisHash)
 {
     return(new X1WalletMetadataFile
     {
         X1WalletAssemblyVersion = GetX1WalletAssemblyVersion(),
         WalletGuid = x1WalletFile.WalletGuid,
         CheckpointHash = genesisHash,
         SyncedHash = genesisHash,
         MemoryPool = new MemoryPoolMetadata {
             Entries = new HashSet <MemoryPoolEntry>()
         }
     });
 }
示例#7
0
        static List <int> GetIndexGaps(int isChange, X1WalletFile x1WalletFile)
        {
            List <int> numbers   = new List <int>();
            var        nextIndex = GetNextIndex(isChange, x1WalletFile, out int[] indexesInUse);

            for (var i = 0; i < nextIndex + C.GapLimit; i++)
            {
                numbers.Add(i);
            }
            for (var k = 0; k < indexesInUse.Length; k++)
            {
                numbers.Remove(indexesInUse[k]);
            }
            return(numbers);
        }
示例#8
0
 public static ISegWitAddress FindAddress(string bech32, X1WalletFile x1WalletFile)
 {
     if (x1WalletFile.PubKeyHashAddresses.TryGetValue(bech32, out var pubKeyHashAddress))
     {
         return(pubKeyHashAddress);
     }
     if (x1WalletFile.ColdStakingAddresses.TryGetValue(bech32, out var coldStakingAddress))
     {
         return(coldStakingAddress);
     }
     if (x1WalletFile.MultiSigAddresses.TryGetValue(bech32, out var multiSigAddress))
     {
         return(multiSigAddress);
     }
     return(null);
 }
示例#9
0
        static void CreateChangeAddressesIfNeeded(string passphrase, X1WalletFile x1WalletFile)
        {
            if (passphrase == null)
            {
                throw new ArgumentNullException(nameof(passphrase));
            }

            var store = x1WalletFile.PubKeyHashAddresses.Values;
            var existingUnusedChangeAddresses = store.Where(ChangeAddressesNeverSeenUsedOnTheChain).ToArray();
            var surplus = existingUnusedChangeAddresses.Length - 1 - C.UnusedChangeAddressBuffer;

            if (surplus != 0 && surplus < C.UnusedChangeAddressBuffer)
            {
                var refill = Math.Abs(surplus);
                CreateAndInsertNewChangeAddresses(passphrase, refill, x1WalletFile);
            }
        }
示例#10
0
        internal static void EnsureDefaultMultisigAddress(string passphrase, X1WalletFile x1WalletFile)
        {
            //if (x1WalletFile.ColdStakingAddresses.Count > 0)
            //    return;

            var decryptedSeed = VCL.DecryptWithPassphrase(passphrase, x1WalletFile.HdSeed);

            KeyMaterial myKeyMaterial = KeyHelper.CreateHdKeyMaterial(decryptedSeed, passphrase, C.Network.Consensus.CoinType, AddressType.MultiSig, C.External, 0);
            PubKey      myPubKey      = myKeyMaterial.GetKey(passphrase).PubKey.Compress();

            // other Key
            KeyMaterial otherKeyMaterial = KeyHelper.CreateHdKeyMaterial(decryptedSeed, passphrase, C.Network.Consensus.CoinType, AddressType.MultiSig, C.External, 1);
            var         otherPubKey      = otherKeyMaterial.GetKey(passphrase).PubKey.Compress();

            // The redeem script looks like:
            // 1 03fad6426522dbda5c5a9f8cab24a54ccc374517ad8790bf7e5a14308afc1bf77b 0340ecf2e20978075a49369e35269ecf0651d2f48061ebbf918f3eb1964051f65c 2 OP_CHECKMULTISIG
            Script redeemScript = PayToMultiSigTemplate.Instance.GenerateScriptPubKey(1, myPubKey, otherPubKey);

            // The address looks like:
            // odx1qvar8r29r8llzj53q5utmcewpju59263h38250ws33lp2q45lmalqg5lmdd
            string bech32ScriptAddress = redeemScript.WitHash.GetAddress(C.Network).ToString();

            // In P2SH payments, we refer to the hash of the Redeem Script as the scriptPubKey. It looks like:
            // 0 674671a8a33ffe295220a717bc65c19728556a3789d547ba118fc2a0569fdf7e
            Script scriptPubKey = redeemScript.WitHash.ScriptPubKey;
            var    scp          = scriptPubKey.ToString();


            var multiSigAddress = new MultiSigAddress
            {
                OwnKey             = myKeyMaterial, Address = bech32ScriptAddress, AddressType = AddressType.MultiSig,
                Label              = "Default 1-of-2 MultiSig", MaxSignatures = 2,
                LastSeenHeight     = null,
                SignaturesRequired = 1,
                RedeemScriptHex    = redeemScript.ToBytes().ToHexString(),
                ScriptPubKeyHex    = scriptPubKey.ToBytes().ToHexString(),
                OtherPublicKeys    = new System.Collections.Generic.Dictionary <string, string>(),
            };

            multiSigAddress.OtherPublicKeys.Add(otherPubKey.ToBytes().ToHexString(), "Bob");



            x1WalletFile.MultiSigAddresses[multiSigAddress.Address] = multiSigAddress;
        }
示例#11
0
        internal static void EnsureDefaultColdStakingAddress(string passphrase, X1WalletFile x1WalletFile)
        {
            //if (x1WalletFile.ColdStakingAddresses.Count > 0)
            //    return;

            var decryptedSeed = VCL.DecryptWithPassphrase(passphrase, x1WalletFile.HdSeed);

            var coldKey = KeyHelper.CreateHdKeyMaterial(decryptedSeed, passphrase, C.Network.Consensus.CoinType, AddressType.ColdStakingCold,
                                                        C.External, 0);

            var hotKey = KeyHelper.CreateHdKeyMaterial(decryptedSeed, passphrase, C.Network.Consensus.CoinType, AddressType.ColdStakingHot,
                                                       C.External, 0);

            PubKey coldPubKey = coldKey.GetKey(passphrase).PubKey.Compress();

            Key    hotPrivateKey = hotKey.GetKey(passphrase);
            PubKey hotPubKey     = hotPrivateKey.PubKey.Compress();

            Script csRedeemScript = ColdStakingScriptTemplate.Instance.GenerateScriptPubKey(hotPubKey.WitHash.AsKeyId(), coldPubKey.WitHash.AsKeyId());

            string csScriptAddress = csRedeemScript.WitHash.GetAddress(C.Network).ToString();

            Script csScriptAddressScriptPubKey = csRedeemScript.WitHash.ScriptPubKey;

            var csAddress = new ColdStakingAddress
            {
                ColdKey         = coldKey,
                HotKey          = hotKey,
                StakingKey      = hotPrivateKey.ToBytes(),
                Label           = "Default CS",
                AddressType     = AddressType.ColdStakingHot, // TODO: which address type?
                Address         = csScriptAddress,
                LastSeenHeight  = null,
                ScriptPubKeyHex = csScriptAddressScriptPubKey.ToBytes().ToHexString(),
                RedeemScriptHex = csRedeemScript.ToBytes().ToHexString()
            };

            if (csAddress.AddressType == AddressType.ColdStakingHot)
            {
                csAddress.StakingKey = hotPrivateKey.ToBytes();
            }

            x1WalletFile.ColdStakingAddresses[csAddress.Address] = csAddress;
        }
示例#12
0
        static int GetNextIndex(int isChange, X1WalletFile x1WalletFile, out int[] indexesInUse)
        {
            indexesInUse = GetIndexesInUse(isChange, x1WalletFile).ToArray();
            var count = indexesInUse.Length;

            var max = 0;

            if (count > 0)
            {
                max = indexesInUse.Max();
            }

            var next = Math.Max(count, max);

            if (count != max)
            {
                Log.Logger.LogWarning(
                    $"For addresses oy type {isChange}, the count of {count} does not equal max of {max}. Using index {next} for the next address to be created.");
            }
            return(next);
        }
示例#13
0
        public static X1WalletMetadataFile LoadOrCreateX1WalletMetadataFile(string x1WalletMetadataFilePath,
                                                                            X1WalletFile x1WalletFile, uint256 genesisHash)
        {
            X1WalletMetadataFile x1WalletMetadataFile;

            if (File.Exists(x1WalletMetadataFilePath))
            {
                x1WalletMetadataFile =
                    Serializer.Deserialize <X1WalletMetadataFile>(File.ReadAllText(x1WalletMetadataFilePath));
                if (x1WalletMetadataFile.X1WalletAssemblyVersion == GetX1WalletAssemblyVersion() &&
                    x1WalletMetadataFile.WalletGuid == x1WalletFile.WalletGuid)
                {
                    return(x1WalletMetadataFile);
                }
                return(CreateX1WalletMetadataFile(x1WalletFile, genesisHash));
            }

            x1WalletMetadataFile = x1WalletFile.CreateX1WalletMetadataFile(genesisHash);
            x1WalletMetadataFile.SaveX1WalletMetadataFile(x1WalletMetadataFilePath);
            return(x1WalletMetadataFile);
        }
示例#14
0
        internal static ColdStakingAddress[] GetAllColdStakingAddresses(int skip, int?take, X1WalletFile x1WalletFile)
        {
            var store  = x1WalletFile.ColdStakingAddresses.Values;
            var filter = AllColdStakingAddresses; // TODO

            return(take.HasValue
                ? store.Where(filter).Skip(skip).Take(take.Value).ToArray()
                : store.Where(filter).ToArray());
        }
示例#15
0
 static void EnsureAddressBuffer(X1WalletFile x1WalletFile)
 {
     //this.GetUnusedReceiveAddress()
     //this.X1WalletFile.CreateNewAddresses();
 }
示例#16
0
        public void CreateWallet(WalletCreateRequest walletCreateRequest)
        {
            string walletName = walletCreateRequest.WalletName;
            string filePath   = walletName.GetX1WalletFilepath(C.Network, this.nodeServices.DataFolder);
            string passphrase = walletCreateRequest.Passphrase;

            if (File.Exists(filePath))
            {
                throw new InvalidOperationException(
                          $"A wallet with the name {walletName} already exists at {filePath}!");
            }

            if (string.IsNullOrWhiteSpace(passphrase))
            {
                throw new InvalidOperationException("A passphrase is required.");
            }

            DateTime now = DateTime.UtcNow;

            var x1WalletFile = new X1WalletFile
            {
                WalletGuid          = Guid.NewGuid(),
                WalletName          = walletName,
                CoinTicker          = C.Network.CoinTicker,
                CoinType            = C.Network.Consensus.CoinType,
                CreatedUtc          = now.ToUnixTime(),
                ModifiedUtc         = now.ToUnixTime(),
                LastBackupUtc       = null,
                Comment             = "Your notes here!",
                Version             = C.WalletKeyFileVersion,
                PassphraseChallenge = KeyHelper.GenerateRandomKeyMaterial(passphrase, 32)
                                      .EncryptedPrivateKey,
                CurrentPath = filePath
            };


            byte[] mnemonicBytes   = KeyHelper.GetRandom(32);
            string bip39Passphrase = walletCreateRequest.Bip39Passphrase?.Trim() ?? "";

            Wordlist wl       = Wordlist.English;
            var      mnemonic = new Mnemonic(wl, mnemonicBytes);

            byte[] hdSeed = mnemonic.DeriveSeed(bip39Passphrase);

            x1WalletFile.HdSeed = VCL.EncryptWithPassphrase(passphrase, hdSeed);
            x1WalletFile.HdSeedHasBip39Passphrase = !string.IsNullOrWhiteSpace(bip39Passphrase);

            // Create one receive addresses, so that GetUsedReceiveAddresses returns at least one address, even if it is not used in this case.
            AddressService.CreateAndInsertNewReceiveAddress("Default address", passphrase, x1WalletFile);

            AddressService.CreateAndInsertNewChangeAddresses(passphrase, C.UnusedChangeAddressBuffer, x1WalletFile);

            ColdStakingAddressService.EnsureDefaultColdStakingAddress(passphrase, x1WalletFile);
            MultiSigAddressService.EnsureDefaultMultisigAddress(passphrase, x1WalletFile);


            AddressService.TryUpdateLookAhead(passphrase, x1WalletFile);

            x1WalletFile.SaveX1WalletFile(filePath);

            X1WalletMetadataFile x1WalletMetadataFile =
                x1WalletFile.CreateX1WalletMetadataFile(C.Network.GenesisHash);
            string x1WalletMetadataFilename = walletName.GetX1WalletMetaDataFilepath(C.Network, this.nodeServices.DataFolder);

            x1WalletMetadataFile.SaveX1WalletMetadataFile(x1WalletMetadataFilename);
        }
示例#17
0
        static void Update(X1WalletFile conversion)
        {
            throw new X1WalletException(System.Net.HttpStatusCode.InternalServerError,
                                        $"An upgrade from version {conversion.Version} to a version {C.WalletKeyFileVersion} is not supported :-/");

            #region sample backup strategy that cannot be compiled any more

            //foreach (var p2WpkhAddress in conversion.Addresses.Values)
            //{
            //    var keyMaterial = new KeyMaterial
            //    {
            //        EncryptedPrivateKey = p2WpkhAddress.EncryptedPrivateKey,
            //        CreatedUtc = DateTime.UtcNow.AddYears(-1).ToUnixTime(),
            //        AddressIndex = null,
            //        IsChange = null,
            //        KeyPath = null,
            //        KeyType = KeyType.Generated
            //    };

            //    var scriptPubKey = new PubKey(p2WpkhAddress.CompressedPublicKey).WitHash.ScriptPubKey;

            //    var pubKeyHashAddress = new PubKeyHashAddress
            //    {
            //        KeyMaterial = keyMaterial,
            //        AddressType = AddressType.PubKeyHash,
            //        FirstSeenUtc = null,
            //        Label = null,
            //        ScriptPubKeyHex = scriptPubKey.ToBytes().ToHexString(),
            //        Address = scriptPubKey.GetAddressFromScriptPubKey()
            //    };

            //    conversion.PubKeyHashAddresses.Add(pubKeyHashAddress.Address, pubKeyHashAddress);
            //}

            //foreach (var scriptAddress in conversion.ScriptAddresses.Values)
            //{
            //    var keyMaterial = new KeyMaterial
            //    {
            //        EncryptedPrivateKey = scriptAddress.EncryptedPrivateKey,
            //        CreatedUtc = DateTime.UtcNow.AddYears(-1).ToUnixTime(),
            //        AddressIndex = null,
            //        IsChange = null,
            //        KeyPath = null,
            //        KeyType = KeyType.Generated
            //    };

            //    var ms = new MultiSigAddress
            //    {
            //        OwnKey = keyMaterial,
            //        MaxSignatures = 2,
            //        SignaturesRequired = 1,
            //        AddressType = AddressType.MultiSig,
            //        Label = scriptAddress.Description,
            //        Address = scriptAddress.Address,
            //        RedeemScriptHex = scriptAddress.RedeemScript.ToHexString(),
            //        ScriptPubKeyHex = scriptAddress.ScriptPubKey.ToHexString(),
            //        OtherPublicKeys = new Dictionary<string, string>()
            //    };
            //    Debug.Assert(scriptAddress.PartnerPublicKeys.Length == 1);
            //    ms.OtherPublicKeys.Add(scriptAddress.PartnerPublicKeys[0].CompressedPublicKey.ToHexString(),
            //        scriptAddress.PartnerPublicKeys[0].Label);

            //    conversion.MultiSigAddresses.Add(ms.Address, ms);
            //}
            //conversion.ScriptAddresses = null;
            //conversion.Addresses = null;

            #endregion
        }
示例#18
0
        /// <summary>
        /// We assume that all PubKeyHashAddress addresses in this file are already used. Therefore this method
        /// should only be called when the wallet is fully up-to-date so that all used addresses are already discovered.
        /// </summary>
        public static List <PubKeyHashAddress> CreateAndInsertNewChangeAddresses(string passphrase, int addressesToCreate, X1WalletFile x1WalletFile)
        {
            var decryptedSeed = VCL.DecryptWithPassphrase(passphrase, x1WalletFile.HdSeed);

            var nextIndex = GetNextIndex(C.Change, x1WalletFile, out var _);

            var created      = 0;
            var newAddresses = new List <PubKeyHashAddress>(addressesToCreate);

            while (created < addressesToCreate)
            {
                var address = GeneratePubKeyHashAddress(decryptedSeed, passphrase, x1WalletFile.CoinType, C.Change, nextIndex);
                newAddresses.Add(address);
                if (!x1WalletFile.PubKeyHashAddresses.TryAdd(address.Address, address))
                {
                    Log.Logger.LogWarning($"Change address {address.Address} already existed - nothing was added.");
                }
                created++;
                nextIndex++;
            }

            x1WalletFile.SaveX1WalletFile(x1WalletFile.CurrentPath);
            return(newAddresses);
        }
示例#19
0
        /// <summary>
        /// TODO: Ensure that also addresses that appear in the memory pool are marked as 'used'.
        /// </summary>
        public static PubKeyHashAddress GetChangeAddress(string passphrase, bool isSafeDummyForPreview, X1WalletFile x1WalletFile)
        {
            if (!string.IsNullOrWhiteSpace(passphrase))
            {
                CreateChangeAddressesIfNeeded(passphrase, x1WalletFile);
            }

            var store = x1WalletFile.PubKeyHashAddresses.Values;

            // If we have an existing unused change address, we just use it.
            var existingUnusedChangeAddresses = store.Where(ChangeAddressesNeverSeenUsedOnTheChain).ToArray();

            if (existingUnusedChangeAddresses.Length > 0)
            {
                return(existingUnusedChangeAddresses[0]);
            }

            if (isSafeDummyForPreview)
            {
                // If we just need an address to test the transaction or estimate the fee,
                // we never create a new change address (even if we could, because we have the passphrase).
                // For safety reasons, we'll use an address we own anyway.
                var anyAddressWeOwn
                    = store.FirstOrDefault(x => x.KeyMaterial.IsChange == C.Change && x.KeyMaterial.KeyType == KeyType.Hd)                   // prefer a hd change address
                      ?? store.FirstOrDefault(x => x.KeyMaterial.IsChange == C.External && x.KeyMaterial.KeyType == KeyType.Hd)              // then a hd external address
                      ?? store.FirstOrDefault(x => x.KeyMaterial.KeyType == KeyType.Generated || x.KeyMaterial.KeyType == KeyType.Imported); // then non-hd

                if (anyAddressWeOwn != null)
                {
                    return(anyAddressWeOwn);
                }

                throw new X1WalletException(HttpStatusCode.InternalServerError, "No address available. To recover from this error, please unlock your wallet so that addresses can be generated.");
            }

            throw new X1WalletException("No unused change address available, please unlock you wallet so that we can create one.");
        }
示例#20
0
        internal static PubKeyHashAddress[] GetAllPubKeyHashReceiveAddresses(int skip, int?take, X1WalletFile x1WalletFile)
        {
            var store  = x1WalletFile.PubKeyHashAddresses.Values;
            var filter = AllPubKeyHashReceiveAddresses;

            return(take.HasValue
                ? store.Where(filter).Skip(skip).Take(take.Value).ToArray()
                : store.Where(filter).ToArray());
        }
示例#21
0
        public static void SaveX1WalletFile(this X1WalletFile x1WalletFile, string filePath)
        {
            var serializedWallet = Serializer.Serialize(x1WalletFile);

            File.WriteAllText(filePath, serializedWallet);
        }
示例#22
0
        /// <summary>
        /// Wallet must be in perfect state, must be called locked.
        /// </summary>
        public static PubKeyHashAddress CreateAndInsertNewReceiveAddress(string label, string passphrase, X1WalletFile x1WalletFile)
        {
            if (!IsLabelUnique(label, x1WalletFile))
            {
                throw new X1WalletException($"The label '{label}' is already in use");
            }

            var nextIndex = GetNextIndex(C.External, x1WalletFile, out var _);

            var decryptedSeed = VCL.DecryptWithPassphrase(passphrase, x1WalletFile.HdSeed);
            var address       = GeneratePubKeyHashAddress(decryptedSeed, passphrase, x1WalletFile.CoinType, C.External, nextIndex);

            if (!x1WalletFile.PubKeyHashAddresses.TryAdd(address.Address, address))
            {
                throw new X1WalletException(HttpStatusCode.InternalServerError, $"Receive address {address.Address} already exists.");
            }

            x1WalletFile.SaveX1WalletFile(x1WalletFile.CurrentPath);
            return(address);
        }