Skip to content

Commit

Permalink
popm/wasm: add 'parseKey' method (hemilabs#168)
Browse files Browse the repository at this point in the history
  • Loading branch information
joshuasing authored Jul 4, 2024
1 parent d39ebe2 commit 89f0345
Show file tree
Hide file tree
Showing 7 changed files with 165 additions and 54 deletions.
12 changes: 10 additions & 2 deletions web/packages/pop-miner/src/browser/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import type {
BitcoinBalanceResult,
BitcoinInfoResult,
BitcoinUTXOsResult,
GenerateKeyResult,
KeyResult,
L2KeystonesResult,
PingResult,
VersionResult,
Expand All @@ -31,7 +31,15 @@ export const generateKey: typeof types.generateKey = ({ network }) => {
return dispatch({
method: 'generateKey',
network: network,
}) as Promise<GenerateKeyResult>;
}) as Promise<KeyResult>;
};

export const parseKey: typeof types.parseKey = ({ network, privateKey }) => {
return dispatch({
method: 'parseKey',
network: network,
privateKey: privateKey,
}) as Promise<KeyResult>;
};

export const startPoPMiner: typeof types.startPoPMiner = (args) => {
Expand Down
1 change: 1 addition & 0 deletions web/packages/pop-miner/src/browser/wasm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export type Method =
| 'version'
| 'wasmPing'
| 'generateKey'
| 'parseKey'
| 'startPoPMiner'
| 'stopPoPMiner'
| 'ping'
Expand Down
55 changes: 42 additions & 13 deletions web/packages/pop-miner/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};

/**
Expand All @@ -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;
};
Expand All @@ -155,9 +162,31 @@ export type GenerateKeyResult = {
*
* @param args Key generation parameters.
*/
export declare function generateKey(
args: GenerateKeyArgs,
): Promise<GenerateKeyResult>;
export declare function generateKey(args: GenerateKeyArgs): Promise<KeyResult>;

/**
* @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<KeyResult>;

/**
* @see startPoPMiner
Expand All @@ -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;

Expand Down
23 changes: 12 additions & 11 deletions web/popminer/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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"`
}

Expand Down
96 changes: 68 additions & 28 deletions web/popminer/dispatch.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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{
Expand Down Expand Up @@ -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")
Expand Down
12 changes: 12 additions & 0 deletions web/www/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,18 @@
<pre class="GenerateKeyShow"></pre>
</div>

<div>
<input id="ParseKeyNetworkInput" value="testnet3">
<label for="ParseKeyNetworkInput">bitcoin network</label>
<br>
<input id="ParseKeyPrivateKeyInput" type="password">
<label for="ParseKeyPrivateKeyInput">secp256k1 private key (hexadecimal string, 32 bytes)</label>
<br>

<button id="ParseKeyButton">parse key</button>
<pre class="ParseKeyShow"></pre>
</div>

<div>
<input id="StartPopMinerLogLevelInput" value="hemiwasm=INFO:popm=INFO:protocol=INFO">
<label for="StartPopMinerLogLevelInput">log level</label>
Expand Down
20 changes: 20 additions & 0 deletions web/www/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');

Expand Down

0 comments on commit 89f0345

Please sign in to comment.