diff --git a/web/packages/pop-miner/src/browser/index.ts b/web/packages/pop-miner/src/browser/index.ts index 5d296cc5..52b061df 100644 --- a/web/packages/pop-miner/src/browser/index.ts +++ b/web/packages/pop-miner/src/browser/index.ts @@ -9,7 +9,7 @@ import type { BitcoinBalanceResult, BitcoinInfoResult, BitcoinUTXOsResult, - GenerateKeyResult, + KeyResult, L2KeystonesResult, PingResult, VersionResult, @@ -31,7 +31,15 @@ export const generateKey: typeof types.generateKey = ({ network }) => { return dispatch({ method: 'generateKey', network: network, - }) as Promise; + }) as Promise; +}; + +export const parseKey: typeof types.parseKey = ({ network, privateKey }) => { + return dispatch({ + method: 'parseKey', + network: network, + privateKey: privateKey, + }) as Promise; }; export const startPoPMiner: typeof types.startPoPMiner = (args) => { diff --git a/web/packages/pop-miner/src/browser/wasm.ts b/web/packages/pop-miner/src/browser/wasm.ts index 2562f3c2..ed5d2015 100644 --- a/web/packages/pop-miner/src/browser/wasm.ts +++ b/web/packages/pop-miner/src/browser/wasm.ts @@ -15,6 +15,7 @@ export type Method = | 'version' | 'wasmPing' | 'generateKey' + | 'parseKey' | 'startPoPMiner' | 'stopPoPMiner' | 'ping' diff --git a/web/packages/pop-miner/src/types.ts b/web/packages/pop-miner/src/types.ts index 5e75e050..f841e9ca 100644 --- a/web/packages/pop-miner/src/types.ts +++ b/web/packages/pop-miner/src/types.ts @@ -98,12 +98,12 @@ export type VersionResult = { /** * The version of the WASM PoP Miner. */ - version: string; + readonly version: string; /** * The SHA-1 hash of the Git commit the WASM binary was built from. */ - gitCommit: string; + readonly gitCommit: string; }; /** @@ -121,30 +121,37 @@ export type GenerateKeyArgs = { network: 'testnet3' | 'mainnet'; }; -export type GenerateKeyResult = { +/** + * Contains a secp256k1 key pair and its corresponding Bitcoin address and + * public key hash, and Ethereum address. + * + * @see generateKey + * @see parseKey + */ +export type KeyResult = { /** - * The Ethereum address for the generated key. + * The Ethereum address for the key. */ readonly ethereumAddress: string; /** - * The network for which the key was generated. + * The network the addresses were created for. */ readonly network: 'testnet3' | 'mainnet'; /** - * The generated secpk256k1 private key, encoded as a hexadecimal string. + * The secp256k1 private key, encoded as a hexadecimal string. */ readonly privateKey: string; /** - * The secpk256k1 public key for the generated key, in the 33-byte compressed - * format, encoded as a hexadecimal string. + * The secp256k1 public key, in the 33-byte compressed format, encoded as a + * hexadecimal string. */ readonly publicKey: string; /** - * The Bitcoin pay-to-pubkey-hash address for the generated key. + * The Bitcoin pay-to-pubkey-hash address for the key. */ readonly publicKeyHash: string; }; @@ -155,9 +162,31 @@ export type GenerateKeyResult = { * * @param args Key generation parameters. */ -export declare function generateKey( - args: GenerateKeyArgs, -): Promise; +export declare function generateKey(args: GenerateKeyArgs): Promise; + +/** + * @see parseKey + */ +export type ParseKeyArgs = { + /** + * Determines which network the public key will be created for. + */ + network: 'testnet3' | 'mainnet'; + + /** + * The private key to parse and return the corresponding public key and + * addresses for, encoded as a hexadecimal string. + */ + privateKey: string; +}; + +/** + * Parses the given private key and returns its corresponding public key and + * addresses. + * + * @param args Key parse parameters. + */ +export declare function parseKey(args: ParseKeyArgs): Promise; /** * @see startPoPMiner @@ -169,7 +198,7 @@ export type MinerStartArgs = { network: 'testnet' | 'devnet' | 'mainnet'; /** - * The secpk256k1 private key for the PoP Miner. + * The secp256k1 private key for the PoP Miner. */ privateKey: string; diff --git a/web/popminer/api.go b/web/popminer/api.go index f0fa3ae3..3333d9ac 100644 --- a/web/popminer/api.go +++ b/web/popminer/api.go @@ -15,6 +15,7 @@ const ( // The following can be dispatched at any time. MethodVersion Method = "version" // Retrieve WASM version information MethodGenerateKey Method = "generateKey" // Generate secp256k1 key pair + MethodParseKey Method = "parseKey" // Parses a secp256k1 private and returns key information MethodStartPoPMiner Method = "startPoPMiner" // Start PoP Miner MethodStopPoPMiner Method = "stopPoPMiner" // Stop PoP Miner @@ -90,25 +91,25 @@ type VersionResult struct { GitCommit string `json:"gitCommit"` } -// GenerateKeyResult contains the generated key information. -// Returned by MethodGenerateKey. -type GenerateKeyResult struct { - // EthereumAddress is the Ethereum address for the generated key. +// KeyResult contains a secp256k1 key pair and its corresponding Bitcoin +// address and public key hash, and Ethereum address. +// +// Returned by MethodGenerateKey and MethodParseKey. +type KeyResult struct { + // EthereumAddress is the Ethereum address for the key. EthereumAddress string `json:"ethereumAddress"` - // Network is the network for which the key was generated. + // Network is the network the addresses were created for. Network string `json:"network"` - // PrivateKey is the generated secpk256k1 private key, encoded as a - // hexadecimal string. + // PrivateKey is the secp256k1 private key, encoded as a hexadecimal string. PrivateKey string `json:"privateKey"` - // PublicKey is the generated secp256k1 public key, in the 33-byte - // compressed format, encoded as a hexadecimal string. + // PublicKey is the secp256k1 public key, in the 33-byte compressed format, + // encoded as a hexadecimal string. PublicKey string `json:"publicKey"` - // PublicKeyHash is the Bitcoin pay-to-pubkey-hash address for the generated - // key. + // PublicKeyHash is the Bitcoin pay-to-pubkey-hash address for the key. PublicKeyHash string `json:"publicKeyHash"` } diff --git a/web/popminer/dispatch.go b/web/popminer/dispatch.go index 2a0d5743..11322a26 100644 --- a/web/popminer/dispatch.go +++ b/web/popminer/dispatch.go @@ -17,7 +17,7 @@ import ( "github.com/btcsuite/btcd/btcutil" btcchaincfg "github.com/btcsuite/btcd/chaincfg" - dcrsecpk256k1 "github.com/decred/dcrd/dcrec/secp256k1/v4" + dcrsecp256k1 "github.com/decred/dcrd/dcrec/secp256k1/v4" "github.com/juju/loggo" "github.com/hemilabs/heminetwork/ethereum" @@ -35,6 +35,13 @@ var handlers = map[Method]*Dispatch{ {Name: "network", Type: js.TypeString}, }, }, + MethodParseKey: { + Handler: parseKey, + Required: []DispatchArgs{ + {Name: "network", Type: js.TypeString}, + {Name: "privateKey", Type: js.TypeString}, + }, + }, MethodStartPoPMiner: { Handler: startPoPMiner, Required: []DispatchArgs{ @@ -182,50 +189,83 @@ func generateKey(_ js.Value, args []js.Value) (any, error) { log.Tracef("generatekey") defer log.Tracef("generatekey exit") - var ( - btcChainParams *btcchaincfg.Params - netNormalized string - ) - net := args[0].Get("network").String() - switch net { - case "devnet", "testnet3", "testnet": - btcChainParams = &btcchaincfg.TestNet3Params - netNormalized = "testnet3" - case "mainnet": - btcChainParams = &btcchaincfg.MainNetParams - netNormalized = "mainnet" - default: - return nil, errorWithCode(ErrorCodeInvalidValue, - fmt.Errorf("invalid network: %s", net)) + net, btcChainParams, err := bitcoinNetwork(args[0].Get("network").String()) + if err != nil { + return nil, err } - // TODO(joshuasing): consider alternative as dcrsecpk256k1 package is large. - privKey, err := dcrsecpk256k1.GeneratePrivateKey() + privKey, err := dcrsecp256k1.GeneratePrivateKey() if err != nil { log.Errorf("failed to generate private key: %v", err) return nil, fmt.Errorf("generate secp256k1 private key: %w", err) } - btcAddress, err := btcutil.NewAddressPubKey( - privKey.PubKey().SerializeCompressed(), - btcChainParams, - ) + + compressedPubKey := privKey.PubKey().SerializeCompressed() + btcAddress, err := btcutil.NewAddressPubKey(compressedPubKey, btcChainParams) if err != nil { - log.Errorf("failed to generate btc address: %v", err) - return nil, fmt.Errorf("create BTC address from public key: %w", err) + log.Errorf("failed to create bitcoin address: %v", err) + return nil, fmt.Errorf("create bitcoin address from public key: %w", err) } + return KeyResult{ + EthereumAddress: ethereum.PublicKeyToAddress(compressedPubKey).String(), + Network: net, + PrivateKey: hex.EncodeToString(privKey.Serialize()), + PublicKey: hex.EncodeToString(compressedPubKey), + PublicKeyHash: btcAddress.AddressPubKeyHash().String(), + }, nil +} + +func parseKey(_ js.Value, args []js.Value) (any, error) { + log.Tracef("parseKey") + defer log.Tracef("parseKey exit") + + net, btcChainParams, err := bitcoinNetwork(args[0].Get("network").String()) + if err != nil { + return nil, err + } + + privateKey := args[0].Get("privateKey").String() + b, err := hex.DecodeString(privateKey) + if err != nil { + return nil, errorWithCode(ErrorCodeInvalidValue, + fmt.Errorf("invalid private key: %w", err)) + } + + if len(b) != dcrsecp256k1.PrivKeyBytesLen { + return nil, errorWithCode(ErrorCodeInvalidValue, + fmt.Errorf("invalid private key length: %d", len(b))) + } + + privKey := dcrsecp256k1.PrivKeyFromBytes(b) compressedPubKey := privKey.PubKey().SerializeCompressed() - ethereumAddress := ethereum.PublicKeyToAddress(compressedPubKey).String() + btcAddress, err := btcutil.NewAddressPubKey(compressedPubKey, btcChainParams) + if err != nil { + log.Errorf("failed to create bitcoin address: %v", err) + return nil, fmt.Errorf("create bitcoin address from public key: %w", err) + } - return GenerateKeyResult{ - EthereumAddress: ethereumAddress, - Network: netNormalized, + return KeyResult{ + EthereumAddress: ethereum.PublicKeyToAddress(compressedPubKey).String(), + Network: net, PrivateKey: hex.EncodeToString(privKey.Serialize()), PublicKey: hex.EncodeToString(compressedPubKey), PublicKeyHash: btcAddress.AddressPubKeyHash().String(), }, nil } +func bitcoinNetwork(network string) (string, *btcchaincfg.Params, error) { + switch network { + case "devnet", "testnet3", "testnet": + return "testnet3", &btcchaincfg.TestNet3Params, nil + case "mainnet": + return "mainnet", &btcchaincfg.MainNetParams, nil + default: + return "", nil, errorWithCode(ErrorCodeInvalidValue, + fmt.Errorf("invalid network: %s", network)) + } +} + func startPoPMiner(_ js.Value, args []js.Value) (any, error) { log.Tracef("startPoPMiner") defer log.Tracef("startPoPMiner exit") diff --git a/web/www/index.html b/web/www/index.html index 005554ca..4d72d9fc 100644 --- a/web/www/index.html +++ b/web/www/index.html @@ -26,6 +26,18 @@

     
 
+    
+ + +
+ + +
+ + +

+    
+
diff --git a/web/www/index.js b/web/www/index.js index 410d9295..d6a38aa4 100644 --- a/web/www/index.js +++ b/web/www/index.js @@ -57,6 +57,26 @@ GenerateKeyButton.addEventListener('click', () => { GenerateKey(); }); +const ParseKeyShow = document.querySelector('.ParseKeyShow'); + +async function ParseKey() { + try { + const result = await dispatch({ + method: 'parseKey', + network: ParseKeyNetworkInput.value, + privateKey: ParseKeyPrivateKeyInput.value, + }); + ParseKeyShow.innerText = JSON.stringify(result, null, 2); + } catch (err) { + ParseKeyShow.innerText = 'Promise rejected: \n' + JSON.stringify(err, null, 2); + console.error('Caught exception', err); + } +} + +ParseKeyButton.addEventListener('click', () => { + ParseKey(); +}); + // start pop miner const StartPopMinerShow = document.querySelector('.StartPopMinerShow');