SignCoordinatorStreams
SignCoordinatorStreams dispatches a bi-directional streaming RPC that allows a remote signer to connect to a watch-only node and remotely provide signatures and ECDH results on demand.
Source: watchonlyrpc/watchonly.proto
gRPC
info
This is a bidirectional-streaming RPC
rpc SignCoordinatorStreams (stream SignCoordinatorResponse) returns (stream SignCoordinatorRequest);
REST
| HTTP Method | Path |
|---|---|
| POST | /v2/watchonly/stream |
Code Samples
- gRPC
- REST
- Javascript
- Python
- grpcurl
const fs = require('fs');
const grpc = require('@grpc/grpc-js');
const protoLoader = require('@grpc/proto-loader');
const GRPC_HOST = 'localhost:10009'
const MACAROON_PATH = 'LND_DIR/data/chain/bitcoin/regtest/admin.macaroon'
const TLS_PATH = 'LND_DIR/tls.cert'
const loaderOptions = {
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true,
};
const packageDefinition = protoLoader.loadSync(['lightning.proto', 'watchonlyrpc/watchonly.proto'], loaderOptions);
const watchonlyrpc = grpc.loadPackageDefinition(packageDefinition).watchonlyrpc;
process.env.GRPC_SSL_CIPHER_SUITES = 'HIGH+ECDSA';
const tlsCert = fs.readFileSync(TLS_PATH);
const sslCreds = grpc.credentials.createSsl(tlsCert);
const macaroon = fs.readFileSync(MACAROON_PATH).toString('hex');
const macaroonCreds = grpc.credentials.createFromMetadataGenerator(function(args, callback) {
let metadata = new grpc.Metadata();
metadata.add('macaroon', macaroon);
callback(null, metadata);
});
let creds = grpc.credentials.combineChannelCredentials(sslCreds, macaroonCreds);
let client = new watchonlyrpc.WatchOnly(GRPC_HOST, creds);
let request = {
ref_request_id: <uint64>,
signer_registration: <SignerRegistration>,
pong: <bool>,
shared_key_response: <SharedKeyResponse>,
sign_message_resp: <SignMessageResp>,
mu_sig2_session_response: <MuSig2SessionResponse>,
mu_sig2_register_nonces_response: <MuSig2RegisterNoncesResponse>,
mu_sig2_comb_nonces_resp: <MuSig2RegisterCombinedNonceResponse>,
mu_sig2_get_comb_nonces_resp: <MuSig2GetCombinedNonceResponse>,
mu_sig2_sign_response: <MuSig2SignResponse>,
mu_sig2_combine_sig_response: <MuSig2CombineSigResponse>,
mu_sig2_cleanup_response: <MuSig2CleanupResponse>,
sign_psbt_response: <SignPsbtResponse>,
signer_error: <SignerError>,
};
let call = client.signCoordinatorStreams({});
call.on('data', function(response) {
// A response was received from the server.
console.log(response);
});
call.on('status', function(status) {
// The current status of the stream.
});
call.on('end', function() {
// The server has closed the stream.
});
call.write(request);
// Console output:
// {
// "request_id": <uint64>,
// "registration_response": <RegistrationResponse>,
// "ping": <bool>,
// "shared_key_request": <SharedKeyRequest>,
// "sign_message_req": <SignMessageReq>,
// "mu_sig2_session_request": <MuSig2SessionRequest>,
// "mu_sig2_register_nonces_request": <MuSig2RegisterNoncesRequest>,
// "mu_sig2_combined_nonces_req": <MuSig2RegisterCombinedNonceRequest>,
// "mu_sig2_get_combined_nonces_req": <MuSig2GetCombinedNonceRequest>,
// "mu_sig2_sign_request": <MuSig2SignRequest>,
// "mu_sig2_combine_sig_request": <MuSig2CombineSigRequest>,
// "mu_sig2_cleanup_request": <MuSig2CleanupRequest>,
// "sign_psbt_request": <SignPsbtRequest>,
// }
import codecs, grpc, os
# Generate the following 2 modules by compiling the watchonlyrpc/watchonly.proto with the grpcio-tools.
# See https://github.com/lightningnetwork/lnd/blob/master/docs/grpc/python.md for instructions.
import watchonly_pb2 as watchonlyrpc, watchonly_pb2_grpc as watchonlystub
GRPC_HOST = 'localhost:10009'
MACAROON_PATH = 'LND_DIR/data/chain/bitcoin/regtest/admin.macaroon'
TLS_PATH = 'LND_DIR/tls.cert'
# create macaroon credentials
macaroon = codecs.encode(open(MACAROON_PATH, 'rb').read(), 'hex')
def metadata_callback(context, callback):
callback([('macaroon', macaroon)], None)
auth_creds = grpc.metadata_call_credentials(metadata_callback)
# create SSL credentials
os.environ['GRPC_SSL_CIPHER_SUITES'] = 'HIGH+ECDSA'
cert = open(TLS_PATH, 'rb').read()
ssl_creds = grpc.ssl_channel_credentials(cert)
# combine macaroon and SSL credentials
combined_creds = grpc.composite_channel_credentials(ssl_creds, auth_creds)
# make the request
channel = grpc.secure_channel(GRPC_HOST, combined_creds)
stub = watchonlystub.WatchOnlyStub(channel)
# Define a generator that returns an Iterable of SignCoordinatorResponse objects.
def request_generator():
# Initialization code here.
while True:
# Parameters here can be set as arguments to the generator.
request = watchonlyrpc.SignCoordinatorResponse(
ref_request_id=<uint64>,
signer_registration=<SignerRegistration>,
pong=<bool>,
shared_key_response=<SharedKeyResponse>,
sign_message_resp=<SignMessageResp>,
mu_sig2_session_response=<MuSig2SessionResponse>,
mu_sig2_register_nonces_response=<MuSig2RegisterNoncesResponse>,
mu_sig2_comb_nonces_resp=<MuSig2RegisterCombinedNonceResponse>,
mu_sig2_get_comb_nonces_resp=<MuSig2GetCombinedNonceResponse>,
mu_sig2_sign_response=<MuSig2SignResponse>,
mu_sig2_combine_sig_response=<MuSig2CombineSigResponse>,
mu_sig2_cleanup_response=<MuSig2CleanupResponse>,
sign_psbt_response=<SignPsbtResponse>,
signer_error=<SignerError>,
)
yield request
# Do things between iterations here.
request_iterable = request_generator()
for response in stub.SignCoordinatorStreams(request_iterable):
print(response)
# {
# "request_id": <uint64>,
# "registration_response": <RegistrationResponse>,
# "ping": <bool>,
# "shared_key_request": <SharedKeyRequest>,
# "sign_message_req": <SignMessageReq>,
# "mu_sig2_session_request": <MuSig2SessionRequest>,
# "mu_sig2_register_nonces_request": <MuSig2RegisterNoncesRequest>,
# "mu_sig2_combined_nonces_req": <MuSig2RegisterCombinedNonceRequest>,
# "mu_sig2_get_combined_nonces_req": <MuSig2GetCombinedNonceRequest>,
# "mu_sig2_sign_request": <MuSig2SignRequest>,
# "mu_sig2_combine_sig_request": <MuSig2CombineSigRequest>,
# "mu_sig2_cleanup_request": <MuSig2CleanupRequest>,
# "sign_psbt_request": <SignPsbtRequest>,
# }
# grpcurl docs: https://github.com/fullstorydev/grpcurl
# Proto source: https://github.com/lightningnetwork/lnd
GRPC_HOST=localhost:10009
LND_DIR=~/.lnd
LND_SOURCE=path/to/lnd
NETWORK=mainnet
MACAROON_PATH="$LND_DIR/data/chain/bitcoin/$NETWORK/admin.macaroon"
TLS_PATH="$LND_DIR/tls.cert"
# Client streaming or bidirectional streaming RPCs are not easily supported
# by grpcurl. Please use a programming language client for this RPC.
- Javascript
- Python
- curl
const fs = require('fs');
const request = require('request');
const REST_HOST = 'localhost:8080'
const MACAROON_PATH = 'LND_DIR/data/chain/bitcoin/regtest/admin.macaroon'
let requestBody = {
ref_request_id: <string>, // <uint64>
signer_registration: <object>, // <SignerRegistration>
pong: <boolean>, // <bool>
shared_key_response: <object>, // <SharedKeyResponse>
sign_message_resp: <object>, // <SignMessageResp>
mu_sig2_session_response: <object>, // <MuSig2SessionResponse>
mu_sig2_register_nonces_response: <object>, // <MuSig2RegisterNoncesResponse>
mu_sig2_comb_nonces_resp: <object>, // <MuSig2RegisterCombinedNonceResponse>
mu_sig2_get_comb_nonces_resp: <object>, // <MuSig2GetCombinedNonceResponse>
mu_sig2_sign_response: <object>, // <MuSig2SignResponse>
mu_sig2_combine_sig_response: <object>, // <MuSig2CombineSigResponse>
mu_sig2_cleanup_response: <object>, // <MuSig2CleanupResponse>
sign_psbt_response: <object>, // <SignPsbtResponse>
signer_error: <object>, // <SignerError>
};
let options = {
url: `https://${REST_HOST}/v2/watchonly/stream`,
// Work-around for self-signed certificates.
rejectUnauthorized: false,
json: true,
headers: {
'Grpc-Metadata-macaroon': fs.readFileSync(MACAROON_PATH).toString('hex'),
},
form: JSON.stringify(requestBody),
}
request.post(options, function(error, response, body) {
console.log(body);
});
// Console output:
// {
// "request_id": <string>, // <uint64>
// "registration_response": <object>, // <RegistrationResponse>
// "ping": <boolean>, // <bool>
// "shared_key_request": <object>, // <SharedKeyRequest>
// "sign_message_req": <object>, // <SignMessageReq>
// "mu_sig2_session_request": <object>, // <MuSig2SessionRequest>
// "mu_sig2_register_nonces_request": <object>, // <MuSig2RegisterNoncesRequest>
// "mu_sig2_combined_nonces_req": <object>, // <MuSig2RegisterCombinedNonceRequest>
// "mu_sig2_get_combined_nonces_req": <object>, // <MuSig2GetCombinedNonceRequest>
// "mu_sig2_sign_request": <object>, // <MuSig2SignRequest>
// "mu_sig2_combine_sig_request": <object>, // <MuSig2CombineSigRequest>
// "mu_sig2_cleanup_request": <object>, // <MuSig2CleanupRequest>
// "sign_psbt_request": <object>, // <SignPsbtRequest>
// }
// --------------------------
// Example with websockets:
// --------------------------
const WebSocket = require('ws');
const fs = require('fs');
const REST_HOST = 'localhost:8080'
const MACAROON_PATH = 'LND_DIR/data/chain/bitcoin/regtest/admin.macaroon'
let ws = new WebSocket(`wss://${REST_HOST}/v2/watchonly/stream?method=POST`, {
// Work-around for self-signed certificates.
rejectUnauthorized: false,
headers: {
'Grpc-Metadata-Macaroon': fs.readFileSync(MACAROON_PATH).toString('hex'),
},
});
let requestBody = {
ref_request_id: <uint64>, // <uint64>
signer_registration: <SignerRegistration>, // <SignerRegistration>
pong: <bool>, // <bool>
shared_key_response: <SharedKeyResponse>, // <SharedKeyResponse>
sign_message_resp: <SignMessageResp>, // <SignMessageResp>
mu_sig2_session_response: <MuSig2SessionResponse>, // <MuSig2SessionResponse>
mu_sig2_register_nonces_response: <MuSig2RegisterNoncesResponse>, // <MuSig2RegisterNoncesResponse>
mu_sig2_comb_nonces_resp: <MuSig2RegisterCombinedNonceResponse>, // <MuSig2RegisterCombinedNonceResponse>
mu_sig2_get_comb_nonces_resp: <MuSig2GetCombinedNonceResponse>, // <MuSig2GetCombinedNonceResponse>
mu_sig2_sign_response: <MuSig2SignResponse>, // <MuSig2SignResponse>
mu_sig2_combine_sig_response: <MuSig2CombineSigResponse>, // <MuSig2CombineSigResponse>
mu_sig2_cleanup_response: <MuSig2CleanupResponse>, // <MuSig2CleanupResponse>
sign_psbt_response: <SignPsbtResponse>, // <SignPsbtResponse>
signer_error: <SignerError>, // <SignerError>
};
ws.on('open', function() {
ws.send(JSON.stringify(requestBody));
});
ws.on('error', function(err) {
console.log('Error: ' + err);
});
ws.on('message', function(body) {
console.log(body);
});
// Console output:
// {
// "request_id": <string>, // <uint64>
// "registration_response": <object>, // <RegistrationResponse>
// "ping": <boolean>, // <bool>
// "shared_key_request": <object>, // <SharedKeyRequest>
// "sign_message_req": <object>, // <SignMessageReq>
// "mu_sig2_session_request": <object>, // <MuSig2SessionRequest>
// "mu_sig2_register_nonces_request": <object>, // <MuSig2RegisterNoncesRequest>
// "mu_sig2_combined_nonces_req": <object>, // <MuSig2RegisterCombinedNonceRequest>
// "mu_sig2_get_combined_nonces_req": <object>, // <MuSig2GetCombinedNonceRequest>
// "mu_sig2_sign_request": <object>, // <MuSig2SignRequest>
// "mu_sig2_combine_sig_request": <object>, // <MuSig2CombineSigRequest>
// "mu_sig2_cleanup_request": <object>, // <MuSig2CleanupRequest>
// "sign_psbt_request": <object>, // <SignPsbtRequest>
// }
import base64, codecs, json, requests
REST_HOST = 'localhost:8080'
MACAROON_PATH = 'LND_DIR/data/chain/bitcoin/regtest/admin.macaroon'
TLS_PATH = 'LND_DIR/tls.cert'
url = f'https://{REST_HOST}/v2/watchonly/stream'
macaroon = codecs.encode(open(MACAROON_PATH, 'rb').read(), 'hex')
headers = {'Grpc-Metadata-macaroon': macaroon}
data = {
'ref_request_id': <uint64>,
'signer_registration': <SignerRegistration>,
'pong': <bool>,
'shared_key_response': <SharedKeyResponse>,
'sign_message_resp': <SignMessageResp>,
'mu_sig2_session_response': <MuSig2SessionResponse>,
'mu_sig2_register_nonces_response': <MuSig2RegisterNoncesResponse>,
'mu_sig2_comb_nonces_resp': <MuSig2RegisterCombinedNonceResponse>,
'mu_sig2_get_comb_nonces_resp': <MuSig2GetCombinedNonceResponse>,
'mu_sig2_sign_response': <MuSig2SignResponse>,
'mu_sig2_combine_sig_response': <MuSig2CombineSigResponse>,
'mu_sig2_cleanup_response': <MuSig2CleanupResponse>,
'sign_psbt_response': <SignPsbtResponse>,
'signer_error': <SignerError>,
}
r = requests.post(url, headers=headers, stream=True, data=json.dumps(data), verify=TLS_PATH)
for raw_response in r.iter_lines():
json_response = json.loads(raw_response)
print(json_response)
# {
# "request_id": <uint64>,
# "registration_response": <RegistrationResponse>,
# "ping": <bool>,
# "shared_key_request": <SharedKeyRequest>,
# "sign_message_req": <SignMessageReq>,
# "mu_sig2_session_request": <MuSig2SessionRequest>,
# "mu_sig2_register_nonces_request": <MuSig2RegisterNoncesRequest>,
# "mu_sig2_combined_nonces_req": <MuSig2RegisterCombinedNonceRequest>,
# "mu_sig2_get_combined_nonces_req": <MuSig2GetCombinedNonceRequest>,
# "mu_sig2_sign_request": <MuSig2SignRequest>,
# "mu_sig2_combine_sig_request": <MuSig2CombineSigRequest>,
# "mu_sig2_cleanup_request": <MuSig2CleanupRequest>,
# "sign_psbt_request": <SignPsbtRequest>,
# }
REST_HOST=localhost:8080
LND_DIR=~/.lnd
NETWORK=mainnet
MACAROON_PATH="$LND_DIR/data/chain/bitcoin/$NETWORK/admin.macaroon"
TLS_PATH="$LND_DIR/tls.cert"
curl -X POST \
--cacert $TLS_PATH \
-H "Grpc-Metadata-macaroon: $(xxd -ps -u -c 1000 $MACAROON_PATH)" \
-d '{ "ref_request_id": <uint64>, "signer_registration": <SignerRegistration>, "pong": <bool>, "shared_key_response": <SharedKeyResponse>, "sign_message_resp": <SignMessageResp>, "mu_sig2_session_response": <MuSig2SessionResponse>, "mu_sig2_register_nonces_response": <MuSig2RegisterNoncesResponse>, "mu_sig2_comb_nonces_resp": <MuSig2RegisterCombinedNonceResponse>, "mu_sig2_get_comb_nonces_resp": <MuSig2GetCombinedNonceResponse>, "mu_sig2_sign_response": <MuSig2SignResponse>, "mu_sig2_combine_sig_response": <MuSig2CombineSigResponse>, "mu_sig2_cleanup_response": <MuSig2CleanupResponse>, "sign_psbt_response": <SignPsbtResponse>, "signer_error": <SignerError> }' \
https://$REST_HOST/v2/watchonly/stream
Messages
watchonlyrpc.SignCoordinatorResponse
Source: watchonlyrpc/watchonly.proto
| Field | gRPC Type | REST Type | REST Placement |
|---|---|---|---|
ref_request_id | uint64 | string | body |
signer_registration | SignerRegistration | object | body |
pong | bool | boolean | body |
shared_key_response | SharedKeyResponse | object | body |
sign_message_resp | SignMessageResp | object | body |
mu_sig2_session_response | MuSig2SessionResponse | object | body |
mu_sig2_register_nonces_response | MuSig2RegisterNoncesResponse | object | body |
mu_sig2_comb_nonces_resp | MuSig2RegisterCombinedNonceResponse | object | body |
mu_sig2_get_comb_nonces_resp | MuSig2GetCombinedNonceResponse | object | body |
mu_sig2_sign_response | MuSig2SignResponse | object | body |
mu_sig2_combine_sig_response | MuSig2CombineSigResponse | object | body |
mu_sig2_cleanup_response | MuSig2CleanupResponse | object | body |
sign_psbt_response | SignPsbtResponse | object | body |
signer_error | SignerError | object | body |
watchonlyrpc.SignCoordinatorRequest
Source: watchonlyrpc/watchonly.proto
| Field | gRPC Type | REST Type |
|---|---|---|
request_id | uint64 | string |
registration_response | RegistrationResponse | object |
ping | bool | boolean |
shared_key_request | SharedKeyRequest | object |
sign_message_req | SignMessageReq | object |
mu_sig2_session_request | MuSig2SessionRequest | object |
mu_sig2_register_nonces_request | MuSig2RegisterNoncesRequest | object |
mu_sig2_combined_nonces_req | MuSig2RegisterCombinedNonceRequest | object |
mu_sig2_get_combined_nonces_req | MuSig2GetCombinedNonceRequest | object |
mu_sig2_sign_request | MuSig2SignRequest | object |
mu_sig2_combine_sig_request | MuSig2CombineSigRequest | object |
mu_sig2_cleanup_request | MuSig2CleanupRequest | object |
sign_psbt_request | SignPsbtRequest | object |
Nested Messages
signrpc.KeyDescriptor
| Field | gRPC Type | REST Type |
|---|---|---|
raw_key_bytes | bytes | string |
key_loc | KeyLocator | object |
signrpc.KeyLocator
| Field | gRPC Type | REST Type |
|---|---|---|
key_family | int32 | integer |
key_index | int32 | integer |
signrpc.MuSig2CleanupRequest
| Field | gRPC Type | REST Type |
|---|---|---|
session_id | bytes | string |
signrpc.MuSig2CleanupResponse
note
This response has no parameters.
signrpc.MuSig2CombineSigRequest
| Field | gRPC Type | REST Type |
|---|---|---|
session_id | bytes | string |
other_partial_signatures | bytes[] | array |
signrpc.MuSig2CombineSigResponse
| Field | gRPC Type | REST Type |
|---|---|---|
have_all_signatures | bool | boolean |
final_signature | bytes | string |
signrpc.MuSig2GetCombinedNonceRequest
| Field | gRPC Type | REST Type |
|---|---|---|
session_id | bytes | string |
signrpc.MuSig2GetCombinedNonceResponse
| Field | gRPC Type | REST Type |
|---|---|---|
combined_public_nonce | bytes | string |
signrpc.MuSig2RegisterCombinedNonceRequest
| Field | gRPC Type | REST Type |
|---|---|---|
session_id | bytes | string |
combined_public_nonce | bytes | string |
signrpc.MuSig2RegisterCombinedNonceResponse
note
This response has no parameters.
signrpc.MuSig2RegisterNoncesRequest
| Field | gRPC Type | REST Type |
|---|---|---|
session_id | bytes | string |
other_signer_public_nonces | bytes[] | array |
signrpc.MuSig2RegisterNoncesResponse
| Field | gRPC Type | REST Type |
|---|---|---|
have_all_nonces | bool | boolean |
signrpc.MuSig2SessionRequest
| Field | gRPC Type | REST Type |
|---|---|---|
key_loc | KeyLocator | object |
all_signer_pubkeys | bytes[] | array |
other_signer_public_nonces | bytes[] | array |
tweaks | TweakDesc[] | array |
taproot_tweak | TaprootTweakDesc | object |
version | MuSig2Version | string |
pregenerated_local_nonce | bytes | string |
signrpc.MuSig2SessionResponse
| Field | gRPC Type | REST Type |
|---|---|---|
session_id | bytes | string |
combined_key | bytes | string |
taproot_internal_key | bytes | string |
local_public_nonces | bytes | string |
have_all_nonces | bool | boolean |
version | MuSig2Version | string |
signrpc.MuSig2SignRequest
| Field | gRPC Type | REST Type |
|---|---|---|
session_id | bytes | string |
message_digest | bytes | string |
cleanup | bool | boolean |
signrpc.MuSig2SignResponse
| Field | gRPC Type | REST Type |
|---|---|---|
local_partial_signature | bytes | string |
signrpc.SharedKeyRequest
| Field | gRPC Type | REST Type |
|---|---|---|
ephemeral_pubkey | bytes | string |
key_locdeprecated | KeyLocator | object |
key_desc | KeyDescriptor | object |
signrpc.SharedKeyResponse
| Field | gRPC Type | REST Type |
|---|---|---|
shared_key | bytes | string |
signrpc.SignMessageReq
| Field | gRPC Type | REST Type |
|---|---|---|
msg | bytes | string |
key_loc | KeyLocator | object |
double_hash | bool | boolean |
compact_sig | bool | boolean |
schnorr_sig | bool | boolean |
schnorr_sig_tap_tweak | bytes | string |
tag | bytes | string |
signrpc.SignMessageResp
| Field | gRPC Type | REST Type |
|---|---|---|
signature | bytes | string |
signrpc.TaprootTweakDesc
| Field | gRPC Type | REST Type |
|---|---|---|
script_root | bytes | string |
key_spend_only | bool | boolean |
signrpc.TweakDesc
| Field | gRPC Type | REST Type |
|---|---|---|
tweak | bytes | string |
is_x_only | bool | boolean |
walletrpc.SignPsbtRequest
| Field | gRPC Type | REST Type |
|---|---|---|
funded_psbt | bytes | string |
walletrpc.SignPsbtResponse
| Field | gRPC Type | REST Type |
|---|---|---|
signed_psbt | bytes | string |
signed_inputs | uint32[] | array |
watchonlyrpc.RegistrationComplete
| Field | gRPC Type | REST Type |
|---|---|---|
signature | string | string |
registration_info | string | string |
watchonlyrpc.RegistrationResponse
| Field | gRPC Type | REST Type |
|---|---|---|
registration_complete | RegistrationComplete | object |
registration_error | string | string |
watchonlyrpc.SignerError
| Field | gRPC Type | REST Type |
|---|---|---|
error | string | string |
watchonlyrpc.SignerRegistration
| Field | gRPC Type | REST Type |
|---|---|---|
registration_challenge | bytes | string |
registration_info | string | string |
Enums
signrpc.MuSig2Version
| Name | Number |
|---|---|
MUSIG2_VERSION_UNDEFINED | 0 |
MUSIG2_VERSION_V040 | 1 |
MUSIG2_VERSION_V100RC2 | 2 |