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); }
internal static ExportKeysResponse ExportKeys(ExportKeysRequest exportKeysRequest, IReadOnlyCollection <ISegWitAddress> addresses) { var header = new StringBuilder(); header.AppendLine($"Starting export from wallet {exportKeysRequest.WalletName}, network {C.Network.Name} on {DateTime.UtcNow} UTC."); var errors = new StringBuilder(); errors.AppendLine("Errors"); var success = new StringBuilder(); success.AppendLine("Exported Private Key (Hex); Unix Time UTC; IsChange; Address; Label:"); int errorCount = 0; int successCount = 0; try { header.AppendLine($"{addresses.Count} found in wallet."); var enc = new Bech32Encoder($"{C.Network.CoinTicker.ToLowerInvariant()}key"); foreach (var a in addresses) { try { var decryptedKey = VCL.DecryptWithPassphrase(exportKeysRequest.WalletPassphrase, a.GetEncryptedPrivateKey()); if (decryptedKey == null) { errorCount++; header.AppendLine( $"Address '{a.Address}' could not be decrpted with this passphrase."); } else { var privateKey = enc.Encode(0, decryptedKey); success.AppendLine($"{privateKey};{a.Address}"); successCount++; } } catch (Exception e) { header.AppendLine($"Exception processing Address '{a.Address}': {e.Message}"); } } header.AppendLine($"{errorCount} errors occured."); header.AppendLine($"{successCount} addresses with private keys successfully exported."); } catch (Exception e) { errors.AppendLine(e.Message); return(new ExportKeysResponse { Message = $"Export failed because an exception occured: {e.Message}" }); } return(new ExportKeysResponse { Message = $"{header}{Environment.NewLine}{success}{Environment.NewLine}{errors}{Environment.NewLine}" }); }
public static KeyMaterial CreateHdKeyMaterial(byte[] seed, string keyEncryptionPassphrase, int coinType, AddressType addressType, int?isChangeOrInternal, int addressIndex) { CheckBytes(seed, 64); if (string.IsNullOrWhiteSpace(keyEncryptionPassphrase)) { throw new ArgumentException(nameof(keyEncryptionPassphrase)); } var privateExtKey = CreateDerivedPrivateKey(seed, addressIndex, addressType, isChangeOrInternal, coinType, out string keyDerivationPath); var privateKeyBytes = privateExtKey.ToBytes(); CheckBytes(privateKeyBytes, 32); var keyMaterial = new KeyMaterial { EncryptedPrivateKey = VCL.EncryptWithPassphrase(keyEncryptionPassphrase, privateKeyBytes), IsChange = isChangeOrInternal, AddressIndex = addressIndex, CreatedUtc = DateTime.UtcNow.ToUnixTime(), KeyPath = keyDerivationPath, KeyType = KeyType.Hd }; return(keyMaterial); }
protected static ECCModel CreateError(Exception e, RequestObject request) { if (AuthKey == null) { throw new X1WalletException(HttpStatusCode.NoContent, "Please retry later.", null); } var responseObject = new ResponseObject <object>(); if (e is X1WalletException se) { responseObject.Status = (int)se.HttpStatusCode; responseObject.StatusText = se.Message; } else { responseObject.Status = 500; responseObject.StatusText = $"Error: {e.Message}"; } var responseJson = Serialize(responseObject); var responseJsonBytes = responseJson.ToUTF8Bytes(); var cipherV2Bytes = VCL.Encrypt(responseJsonBytes, request.CurrentPublicKey.FromBase64(), VCL.ECKeyPair.PrivateKey, AuthKey.PrivateKey); ECCModel eccModel = new ECCModel { CurrentPublicKey = VCL.ECKeyPair.PublicKey.ToHexString(), CipherV2Bytes = cipherV2Bytes.ToHexString(), AuthKey = AuthKey.PublicKey.ToHexString() }; return(eccModel); }
public static KeyMaterial GenerateRandomKeyMaterial(string keyEncryptionPassphrase, int length) { return(new KeyMaterial { EncryptedPrivateKey = VCL.EncryptWithPassphrase(keyEncryptionPassphrase, GetRandom(length)), KeyPath = null, AddressIndex = null, IsChange = null, CreatedUtc = DateTime.UtcNow.ToUnixTime(), KeyType = KeyType.Generated }); }
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; }
public static KeyMaterial ImportKeyMaterial(byte[] privateKey, string keyEncryptionPassphrase) { CheckBytes(privateKey, 32); if (string.IsNullOrWhiteSpace(keyEncryptionPassphrase)) { throw new ArgumentException(nameof(keyEncryptionPassphrase)); } return(new KeyMaterial { EncryptedPrivateKey = VCL.EncryptWithPassphrase(keyEncryptionPassphrase, privateKey), KeyPath = null, AddressIndex = null, IsChange = null, CreatedUtc = DateTime.UtcNow.ToUnixTime(), KeyType = KeyType.Imported }); }
/// <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); }
protected static DecryptedRequest DecryptRequest(RequestObject request, WalletController walletController) { byte[] decrypted = VCL.Decrypt(request.CipherV2Bytes.FromBase64(), request.CurrentPublicKey.FromBase64(), VCL.ECKeyPair.PrivateKey, AuthKey.PrivateKey); if (decrypted == null) { throw new X1WalletException((HttpStatusCode)427, "Public key changed - please reload", null); } string json = decrypted.FromUTF8Bytes(); DecryptedRequest decryptedRequest = JsonConvert.DeserializeObject <DecryptedRequest>(json); if (((IList)CommandsWithoutWalletNameCheck).Contains(decryptedRequest.Command)) { return(decryptedRequest); } if (decryptedRequest.Target == null) { throw new X1WalletException(HttpStatusCode.BadRequest, "No wallet name was supplied."); } walletController.SetWalletName(decryptedRequest.Target.Replace($".{walletController.CoinTicker}{X1WalletFile.FileExtension}", "")); return(decryptedRequest); }
public override Task InitializeAsync() { var authKeyFile = Path.Combine(this.dataFolder.RootPath, ServerAuthKeyFilename); if (File.Exists(authKeyFile)) { var existing = File.ReadAllText(authKeyFile); ECKeyPair existingAuthKey = JsonConvert.DeserializeObject <ECKeyPair>(existing); SecureApiControllerBase.AuthKey = existingAuthKey; this.logger.LogInformation($"Using existing authentication key from {authKeyFile}."); return(Task.CompletedTask); } ECKeyPair newAuthKey = VCL.Instance().GenerateECKeyPair().Result; var created = JsonConvert.SerializeObject(newAuthKey); File.WriteAllText(authKeyFile, created); SecureApiControllerBase.AuthKey = newAuthKey; this.logger.LogInformation($"Created authentication key in {authKeyFile}."); return(Task.CompletedTask); }
protected static ECCModel CreateOk <T>(T data, RequestObject request) { if (AuthKey == null) { throw new X1WalletException(HttpStatusCode.NoContent, "Please retry later.", null); } var responseObject = new ResponseObject <T> { ResponsePayload = data, Status = 200, StatusText = "OK" }; var responseJson = Serialize(responseObject); var responseJsonBytes = responseJson.ToUTF8Bytes(); var cipherV2Bytes = VCL.Encrypt(responseJsonBytes, request.CurrentPublicKey.FromBase64(), VCL.ECKeyPair.PrivateKey, AuthKey.PrivateKey); ECCModel eccModel = new ECCModel { CurrentPublicKey = VCL.ECKeyPair.PublicKey.ToHexString(), CipherV2Bytes = cipherV2Bytes.ToHexString(), AuthKey = AuthKey.PublicKey.ToHexString() }; return(eccModel); }
public void StartStaking(string passphrase) { if (this.isDisposing) { return; } Guard.NotNull(passphrase, nameof(passphrase)); if (VCL.DecryptWithPassphrase(passphrase, base.GetPassphraseChallenge()) == null) { throw new X1WalletException(HttpStatusCode.Unauthorized, "The passphrase is not correct."); } base.SetStakingPassphrase(passphrase); if (this.stakingService == null) { this.stakingService = new StakingService(this, passphrase, this.nodeServices); this.stakingService.Start(); } }
/// <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); }
public static Key GetPrivateKey(this SegWitCoin segWitCoin, string passphrase) { return(new Key(VCL.DecryptWithPassphrase(passphrase, segWitCoin.SegWitAddress.GetEncryptedPrivateKey()))); }
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); }
public static Key GetKey(this KeyMaterial keyMaterial, string passphrase) { return(new Key(VCL.DecryptWithPassphrase(passphrase, keyMaterial.EncryptedPrivateKey))); }
public static ImportKeysResponse ImportKeys(ImportKeysRequest importKeysRequest, byte[] passphraseChallenge) { if (importKeysRequest == null) { throw new ArgumentNullException(nameof(importKeysRequest)); } if (importKeysRequest.WalletPassphrase == null) { throw new ArgumentNullException(nameof(importKeysRequest.WalletPassphrase)); } if (importKeysRequest.Keys == null) { throw new ArgumentNullException(nameof(importKeysRequest.Keys)); } var delimiters = new HashSet <char>(); foreach (var c in importKeysRequest.Keys.Trim().ToCharArray()) { if (char.IsWhiteSpace(c)) { delimiters.Add(c); } } var items = importKeysRequest.Keys.Split(delimiters.ToArray()); var possibleKeys = items.Where(i => i.Length == 52).Distinct().ToList(); if (possibleKeys.Count == 0) { throw new X1WalletException(HttpStatusCode.BadRequest, "Input material cointained no keys."); } var test = VCL.DecryptWithPassphrase(importKeysRequest.WalletPassphrase, passphraseChallenge); if (test == null) { throw new X1WalletException(HttpStatusCode.Unauthorized, "Your passphrase is incorrect."); } var importedAddresses = new List <string>(); var obsidianNetwork = new ObsidianNetwork(); foreach (var candidate in possibleKeys) { try { var secret = new BitcoinSecret(candidate, obsidianNetwork); var privateKey = secret.PrivateKey.ToBytes(); throw new NotImplementedException(); //var address = AddressHelper.CreateWithPrivateKey(privateKey, importKeysRequest.WalletPassphrase, AddressType.SingleKey); //this.X1WalletFile.Addresses.Add(address.Address, address); //importedAddresses.Add($"{secret.GetAddress()} -> {address.Address}"); } catch (Exception e) { Log.Logger.LogWarning($"Could not import '{candidate}' as key or address. {e.Message}"); } } // this.X1WalletFile.SaveX1WalletFile(this.CurrentX1WalletFilePath); var response = new ImportKeysResponse { ImportedAddresses = importedAddresses, Message = $"Imported {importedAddresses.Count} addresses." }; return(response); }