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); }
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); }
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); }
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)); }
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(); }
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>() } }); }
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); }
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); }
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); } }
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; }
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; }
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); }
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); }
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()); }
static void EnsureAddressBuffer(X1WalletFile x1WalletFile) { //this.GetUnusedReceiveAddress() //this.X1WalletFile.CreateNewAddresses(); }
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); }
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 }
/// <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); }
/// <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."); }
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()); }
public static void SaveX1WalletFile(this X1WalletFile x1WalletFile, string filePath) { var serializedWallet = Serializer.Serialize(x1WalletFile); File.WriteAllText(filePath, serializedWallet); }
/// <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); }