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

popm/wasm: add 'parseKey' method #168

Merged
merged 1 commit into from
Jul 4, 2024
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
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
Loading