Security News
The Risks of Misguided Research in Supply Chain Security
Snyk's use of malicious npm packages for research raises ethical concerns, highlighting risks in public deployment, data exfiltration, and unauthorized testing.
github.com/conduition/wos
wos
API client for the Wallet of Satoshi Bitcoin Lightning app.
Wallet of Satoshi is a custodial Bitcoin Lightning wallet app. It is effectively a web-wallet, because the signing keys are actually hosted on WoS servers, while their mobile app is just a thin API client around their backend sevice.
By using WoS, Bitcoiners trade security for ease-of-use. WoS is well known for being a very beginner-friendly Lightning wallet, due largely to this trade-off. WoS can run off with your money, but you also don't have to worry about running a node, managing channels, updating software, and so forth.
Since WoS is a no-KYC no-signup-required web-wallet, it is very easy to reverse-engineer their API for programmatic use. New wallets can be created on-the-fly with no API credentials needed. Existing wallets can be accessed using simple API credentials.
This library is a Golang package which encapsulates the WoS v1 REST API.
The Wallet
struct type provides a full interface to the WoS API, including creating invoices and sending payments both on-chain and over Lightning.
package main
import (
"context"
"fmt"
"os"
"github.com/conduition/wos"
)
func main() {
ctx := context.Background()
// First, create a wallet from scratch. It will have empty balances
// but you can start depositing right away via lightning.
wallet, creds, err := wos.CreateWallet(ctx, nil)
if err != nil {
panic(err)
}
fmt.Println(wallet.LightningAddress())
// The Credentials should be saved somewhere, so that you can
// regain access to the same wallet later.
os.WriteFile(
"/secure/location/wos-creds",
[]byte(creds.APIToken+"\n"+creds.APISecret),
0o600,
)
// To reopen the wallet after going offline, parse the Credentials
// from the disk, and then use Credentials.OpenWallet.
wallet, err = creds.OpenWallet(ctx, nil)
if err != nil {
panic(err)
}
// Create an invoice.
invoice, err := wallet.NewInvoice(ctx, &wos.InvoiceOptions{
Amount: 0.0001,
Description: "don't actually send money to this invoice.",
})
if err != nil {
panic(err)
}
fmt.Println(invoice.Bolt11)
// Pay an invoice.
payment, err := wallet.PayInvoice(ctx, invoice.Bolt11, "a payment label, can be omitted")
if err != nil {
panic(err)
}
fmt.Println(payment.Status, payment.Amount, payment.Currency, payment.Time)
}
WoS credentials are split into a bearer API token and a shared API secret.
The token is passed as a header with every HTTP request to the WoS API, while the secret is used to produce HMACs for POST requests.
The secret-signature is only required for POST requests which change wallet state - such as creating or paying invoices, GET requests - such as fetching balance or payment history - require only the API token. This means a WoS API client can be segregated into a Reader
and a Signer
.
A Reader
can view a WoS account's balances and ongoing payments in real-time, while A Signer
is an interface type which can be a simple wrapper around the API Secret, or the API secret could live offline or on a more secure machine which validates & signs POST requests, enforcing arbitrary user-defined rules (e.g. only allow max $50 per purchase, or max $1000 per day, etc). Put both together and you get a Wallet
.
The wos
package fully supports this kind of architecture. For example, consider this example with a Signer
which lives on a remote machine. Signatures are fetched via HTTP POST requests.
package main
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"github.com/conduition/wos"
)
type RemoteSigner struct {
URL string
}
func (rs RemoteSigner) SignRequest(
ctx context.Context,
endpoint, nonce, requestBody, apiToken string,
) ([]byte, error) {
bodyBytes, err := json.Marshal(map[string]string{
"endpoint": endpoint,
"nonce": nonce,
"body": requestBody,
})
if err != nil {
return nil, err
}
req, err := http.NewRequestWithContext(ctx, "POST", rs.URL, bytes.NewReader(bodyBytes))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return nil, fmt.Errorf("received status code %d from remote signer", resp.StatusCode)
}
return io.ReadAll(resp.Body)
}
func main() {
reader := wos.NewReader("93b9c574-30a2-4bf5-81ba-f9feadb313a7", nil)
signer := RemoteSigner{"https://somewheresecure.place/api/sign"}
wallet, err := wos.OpenWallet(context.Background(), reader, signer)
if err != nil {
panic(err)
}
fmt.Println(wallet.LightningAddress())
}
FAQs
Unknown package
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Snyk's use of malicious npm packages for research raises ethical concerns, highlighting risks in public deployment, data exfiltration, and unauthorized testing.
Research
Security News
Socket researchers found several malicious npm packages typosquatting Chalk and Chokidar, targeting Node.js developers with kill switches and data theft.
Security News
pnpm 10 blocks lifecycle scripts by default to improve security, addressing supply chain attack risks but sparking debate over compatibility and workflow changes.