From 60a3489dbc21e764d5d6f77f6fb54d86a5da6b76 Mon Sep 17 00:00:00 2001 From: Joshua Sing Date: Sat, 6 Jul 2024 01:23:04 +1000 Subject: [PATCH] popm/wasm: add 'bitcoinAddressToScriptHash' method (#169) --- web/packages/pop-miner/src/browser/index.ts | 10 +++++ web/packages/pop-miner/src/browser/wasm.ts | 1 + web/packages/pop-miner/src/types.ts | 44 +++++++++++++++++++++ web/popminer/api.go | 24 ++++++++--- web/popminer/dispatch.go | 38 ++++++++++++++++++ web/www/index.html | 12 ++++++ web/www/index.js | 20 ++++++++++ 7 files changed, 144 insertions(+), 5 deletions(-) diff --git a/web/packages/pop-miner/src/browser/index.ts b/web/packages/pop-miner/src/browser/index.ts index 52b061df..f03eec2d 100644 --- a/web/packages/pop-miner/src/browser/index.ts +++ b/web/packages/pop-miner/src/browser/index.ts @@ -6,6 +6,7 @@ import * as types from '../types'; import type { + BitcoinAddressToScriptHashResult, BitcoinBalanceResult, BitcoinInfoResult, BitcoinUTXOsResult, @@ -42,6 +43,15 @@ export const parseKey: typeof types.parseKey = ({ network, privateKey }) => { }) as Promise; }; +export const bitcoinAddressToScriptHash: typeof types.bitcoinAddressToScriptHash = + ({ network, address }) => { + return dispatch({ + method: 'bitcoinAddressToScriptHash', + network: network, + address: address, + }) as Promise; + }; + export const startPoPMiner: typeof types.startPoPMiner = (args) => { return dispatchVoid({ method: 'startPoPMiner', diff --git a/web/packages/pop-miner/src/browser/wasm.ts b/web/packages/pop-miner/src/browser/wasm.ts index ed5d2015..ecb2eade 100644 --- a/web/packages/pop-miner/src/browser/wasm.ts +++ b/web/packages/pop-miner/src/browser/wasm.ts @@ -16,6 +16,7 @@ export type Method = | 'wasmPing' | 'generateKey' | 'parseKey' + | 'bitcoinAddressToScriptHash' | 'startPoPMiner' | 'stopPoPMiner' | 'ping' diff --git a/web/packages/pop-miner/src/types.ts b/web/packages/pop-miner/src/types.ts index f841e9ca..acb5c1af 100644 --- a/web/packages/pop-miner/src/types.ts +++ b/web/packages/pop-miner/src/types.ts @@ -188,6 +188,50 @@ export type ParseKeyArgs = { */ export declare function parseKey(args: ParseKeyArgs): Promise; +/** + * @see bitcoinAddressToScriptHash + */ +export type BitcoinAddressToScriptHashArgs = { + /** + * Determines the network the key will be parsed for. + */ + network: 'testnet3' | 'mainnet'; + + /** + * The Bitcoin address to return the script hash of. + */ + address: string; +}; + +/** + * @see bitcoinAddressToScriptHash + */ +export type BitcoinAddressToScriptHashResult = { + /** + * The network the address is for. + */ + network: 'testnet3' | 'mainnet'; + + /** + * The address the script hash is for. + */ + address: string; + + /** + * The script hash for the address. + */ + scriptHash: string; +}; + +/** + * Returns the script hash of the given Bitcoin address. + * + * @param args Bitcoin to script hash arguments. + */ +export declare function bitcoinAddressToScriptHash( + args: BitcoinAddressToScriptHashArgs, +): Promise; + /** * @see startPoPMiner */ diff --git a/web/popminer/api.go b/web/popminer/api.go index 3333d9ac..d9cf591d 100644 --- a/web/popminer/api.go +++ b/web/popminer/api.go @@ -13,11 +13,12 @@ type Method string 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 + 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 + MethodBitcoinAddressToScriptHash Method = "bitcoinAddressToScriptHash" // Bitcoin address to script hash + MethodStartPoPMiner Method = "startPoPMiner" // Start PoP Miner + MethodStopPoPMiner Method = "stopPoPMiner" // Stop PoP Miner // The following can only be dispatched after the PoP Miner is running. MethodPing Method = "ping" // Ping BFG @@ -113,6 +114,19 @@ type KeyResult struct { PublicKeyHash string `json:"publicKeyHash"` } +// BitcoinAddressToScriptHashResult contains the script hash requested for an +// address. +type BitcoinAddressToScriptHashResult struct { + // Network is the network the address is for. + Network string `json:"network"` + + // Address is the address the script hash is for. + Address string `json:"address"` + + // ScriptHash is the script hash for the given address. + ScriptHash string `json:"scriptHash"` +} + // PingResult contains information when pinging the BFG server. // Returned by MethodPing. type PingResult struct { diff --git a/web/popminer/dispatch.go b/web/popminer/dispatch.go index 11322a26..b3b4727c 100644 --- a/web/popminer/dispatch.go +++ b/web/popminer/dispatch.go @@ -8,6 +8,7 @@ package main import ( "context" + "crypto/sha256" "encoding/hex" "errors" "fmt" @@ -17,6 +18,7 @@ import ( "github.com/btcsuite/btcd/btcutil" btcchaincfg "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/txscript" dcrsecp256k1 "github.com/decred/dcrd/dcrec/secp256k1/v4" "github.com/juju/loggo" @@ -42,6 +44,13 @@ var handlers = map[Method]*Dispatch{ {Name: "privateKey", Type: js.TypeString}, }, }, + MethodBitcoinAddressToScriptHash: { + Handler: bitcoinAddressToScriptHash, + Required: []DispatchArgs{ + {Name: "network", Type: js.TypeString}, + {Name: "address", Type: js.TypeString}, + }, + }, MethodStartPoPMiner: { Handler: startPoPMiner, Required: []DispatchArgs{ @@ -254,6 +263,35 @@ func parseKey(_ js.Value, args []js.Value) (any, error) { }, nil } +func bitcoinAddressToScriptHash(_ js.Value, args []js.Value) (any, error) { + log.Tracef("bitcoinAddressToScriptHash") + defer log.Tracef("bitcoinAddressToScriptHash exit") + + net, btcChainParams, err := bitcoinNetwork(args[0].Get("network").String()) + if err != nil { + return nil, err + } + + address := args[0].Get("address").String() + addr, err := btcutil.DecodeAddress(address, btcChainParams) + if err != nil { + return nil, errorWithCode(ErrorCodeInvalidValue, + fmt.Errorf("invalid bitcoin address: %w", err)) + } + + script, err := txscript.PayToAddrScript(addr) + if err != nil { + return nil, fmt.Errorf("convert address to script: %w", err) + } + + scriptHash := sha256.Sum256(script) + return BitcoinAddressToScriptHashResult{ + Network: net, + Address: address, + ScriptHash: hex.EncodeToString(scriptHash[:]), + }, nil +} + func bitcoinNetwork(network string) (string, *btcchaincfg.Params, error) { switch network { case "devnet", "testnet3", "testnet": diff --git a/web/www/index.html b/web/www/index.html index 4d72d9fc..93464d61 100644 --- a/web/www/index.html +++ b/web/www/index.html @@ -38,6 +38,18 @@

     
 
+    
+ + +
+ + +
+ + +

+    
+
diff --git a/web/www/index.js b/web/www/index.js index d6a38aa4..326c2216 100644 --- a/web/www/index.js +++ b/web/www/index.js @@ -77,6 +77,26 @@ ParseKeyButton.addEventListener('click', () => { ParseKey(); }); +const BitcoinAddressToScriptHashAddressShow = document.querySelector('.BitcoinAddressToScriptHashAddressShow'); + +async function BitcoinAddressToScriptHashAddress() { + try { + const result = await dispatch({ + method: 'bitcoinAddressToScriptHash', + network: BitcoinAddressToScriptHashNetworkInput.value, + address: BitcoinAddressToScriptHashAddressInput.value, + }); + BitcoinAddressToScriptHashAddressShow.innerText = JSON.stringify(result, null, 2); + } catch (err) { + BitcoinAddressToScriptHashAddressShow.innerText = 'Promise rejected: \n' + JSON.stringify(err, null, 2); + console.error('Caught exception', err); + } +} + +BitcoinAddressToScriptHashAddressButton.addEventListener('click', () => { + BitcoinAddressToScriptHashAddress(); +}); + // start pop miner const StartPopMinerShow = document.querySelector('.StartPopMinerShow');