Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support WIF encoded private keys #266

Merged
merged 6 commits into from
Mar 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ may not exactly match a publicly released version.

## [3.6] - Unreleased

### Added

* WIF encoded private keys can be used to specify signing and non-signing accounts

## [3.5] - 2023-01-03

### Changed
Expand Down
11 changes: 11 additions & 0 deletions docs/command-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,22 @@ multiple ways:
- `genesis` to use the consensus node multi-sig account which holds the genesis NEO and GAS
- Neo-Express wallet nickname (see `wallet create` below). Note, this includes `node1` etc to specify
the default wallet account associated with each consensus node
- A [WIF encoded](https://developer.bitcoin.org/devguide/wallets.html#wallet-import-format-wif) private key
- A [standard NEP-2 Passphrase-protected private key](https://github.com/neo-project/proposals/blob/master/nep-2.mediawiki).
- When using a NEP-2 protected private key, the passphrase must be specified using the `--password` option
- The path to a [standard NEP-6 JSON wallet](https://github.com/neo-project/proposals/blob/master/nep-6.mediawiki).
- When using a NEP-6 wallet, the password must be specified using the `--password` option.
- Note, Neo-Express only supports NEP-6 wallets with either a single account or a single default account

NEP-2 private key and NEP-6 JSON wallet are password protected. When using one of these methods, the password
can be specified using the `--password` option. If the password is not specified on the command line, Neo-Express
will prompt the user to enter the password.

> Note, `neoxp batch` command does not support interactive prompting. Using a NEP-2 private key or NEP-6 wallet
> with `neoxp batch` also requires specifying the `--password` option. Needless to say, storing a password in
> an unencrpted batch file is not secure, and developers should not use wallets associated with production, mainnet
> assets with Neo-Express.

### Specifying a Non-Signing Account

A account used that is not used for signing doesn't need an accessible private key. Non-Signing accounts
Expand All @@ -36,6 +46,7 @@ can be specified in multiple ways:
- Neo-Express wallet nickname (see `wallet create` below). Note, this includes `node1` etc to specify
the default wallet account associated with each consensus node
- A standard Neo N3 address such as `Ne4Ko2JkzjAd8q2sasXsQCLfZ7nu8Gm5vR`
- A [WIF encoded](https://developer.bitcoin.org/devguide/wallets.html#wallet-import-format-wif) private key

## neoxp create

Expand Down
9 changes: 6 additions & 3 deletions src/neoxp/Commands/WalletCommand.List.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ static void PrintAccountInfo(JsonTextWriter writer, string walletName, Neo.Walle
writer.WriteValue(account.ScriptHash.ToString());
writer.WritePropertyName("private-key");
writer.WriteValue(Convert.ToHexString(keyPair.PrivateKey));
writer.WritePropertyName("private-key-wif");
writer.WriteValue(keyPair.Export());
writer.WritePropertyName("public-key");
writer.WriteValue(Convert.ToHexString(keyPair.PublicKey.EncodePoint(true)));
writer.WriteEndObject();
Expand Down Expand Up @@ -121,9 +123,10 @@ static void PrintAccountInfo(TextWriter writer, Neo.Wallets.WalletAccount accoun
var keyPair = account.GetKey() ?? throw new Exception();

writer.WriteLine($" {account.Address} ({(account.IsDefault ? "Default" : account.Label)})");
writer.WriteLine($" script hash: {BitConverter.ToString(account.ScriptHash.ToArray())}");
writer.WriteLine($" public key: {Convert.ToHexString(keyPair.PublicKey.EncodePoint(true))}");
writer.WriteLine($" private key: {Convert.ToHexString(keyPair.PrivateKey)}");
writer.WriteLine($" script hash: {BitConverter.ToString(account.ScriptHash.ToArray())}");
writer.WriteLine($" public key: {Convert.ToHexString(keyPair.PublicKey.EncodePoint(true))}");
writer.WriteLine($" private key: {Convert.ToHexString(keyPair.PrivateKey)}");
writer.WriteLine($" private key (WIF): {keyPair.Export()}");
}
}

Expand Down
22 changes: 22 additions & 0 deletions src/neoxp/Extensions/ExpressNodeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,29 @@ public static async Task<OneOf<UInt160, None>> TryGetAccountHashAsync(this IExpr
return accountHash;
}

if (TryGetWIFAccountHash(name, out accountHash))
{
return accountHash;
}

return default(None);

static bool TryGetWIFAccountHash(string wif, out UInt160 accountHash)
{
try
{
var privateKey = Wallet.GetPrivateKeyFromWIF(wif);
var keyPair = new KeyPair(privateKey);
var contract = Contract.CreateSignatureContract(keyPair.PublicKey);
accountHash = contract.ScriptHash;
return true;
}
catch
{
accountHash = UInt160.Zero;
return false;
}
}
}

public static async Task<UInt160> ParseAssetAsync(this IExpressNode expressNode, string asset)
Expand Down
38 changes: 32 additions & 6 deletions src/neoxp/Extensions/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,11 @@ public static bool TryGetSigningAccount(this ExpressChainManager chainManager, s
}
}

if (TryGetWIFWallet(name, settings, out wallet, out accountHash))
{
return true;
}

if (!string.IsNullOrEmpty(password))
{
if (TryGetNEP2Wallet(name, password, settings, out wallet, out accountHash))
Expand All @@ -186,14 +191,28 @@ public static bool TryGetSigningAccount(this ExpressChainManager chainManager, s
accountHash = null;
return false;

static bool TryGetWIFWallet(string wif, ProtocolSettings settings, [MaybeNullWhen(false)] out Wallet wallet, [MaybeNullWhen(false)] out UInt160 accountHash)
{
try
{
var privateKey = Wallet.GetPrivateKeyFromWIF(wif);
CreateWallet(privateKey, settings, out wallet, out accountHash);
return true;
}
catch
{
wallet = null;
accountHash = null;
return false;
}
}

static bool TryGetNEP2Wallet(string nep2, string password, ProtocolSettings settings, [MaybeNullWhen(false)] out Wallet wallet, [MaybeNullWhen(false)] out UInt160 accountHash)
{
try
{
var privateKey = Wallet.GetPrivateKeyFromNEP2(nep2, password, settings.AddressVersion);
wallet = new DevWallet(settings, string.Empty);
var account = wallet.CreateAccount(privateKey);
accountHash = account.ScriptHash;
CreateWallet(privateKey, settings, out wallet, out accountHash);
return true;
}
catch
Expand All @@ -214,9 +233,7 @@ static bool TryGetNEP6Wallet(string path, string password, ProtocolSettings sett
?? throw new InvalidOperationException("Neo-express only supports NEP-6 wallets with a single default account or a single account");
if (nep6account.IsMultiSigContract()) throw new Exception("Neo-express doesn't supports multi-sig NEP-6 accounts");
var keyPair = nep6account.GetKey() ?? throw new Exception("account.GetKey() returned null");
wallet = new DevWallet(settings, string.Empty);
var account = wallet.CreateAccount(keyPair.PrivateKey);
accountHash = account.ScriptHash;
CreateWallet(keyPair.PrivateKey, settings, out wallet, out accountHash);
return true;
}
catch
Expand All @@ -226,6 +243,15 @@ static bool TryGetNEP6Wallet(string path, string password, ProtocolSettings sett
return false;
}
}

static void CreateWallet(byte[] privateKey, ProtocolSettings settings, out Wallet wallet, out UInt160 accountHash)
{
wallet = new DevWallet(settings, string.Empty);
var account = wallet.CreateAccount(privateKey);
accountHash = account.ScriptHash;
}


}
}
}