Skip to content

Distributed Signatures

Improved distributed signatures

Initial implementation of distributed signatures support in the node worked well in simple cases, and ZK Treasury built on top of it. However, in complex cases it has some problems:

  • hints generated (such as commitments) were not tied to a position of a sub-expression in a sigma-expression. For example, for statement like "atLeast(2, Coll(pkAlice, pkBob, pkCharlie)) && (pkBob || pkDiana)", the same commitment would be generated for Bob. Which is improper and insecure at all - a signature would reveal Bob's secret key (as the same randomness used twice for different challenges in Schnorr protocols).
  • similarly, hints generated were not tied to inputs.

This is fixed with new API introduced in distributed-sigs branch. Now all the hints tied with input indexes and also with positions in sigma-tree after script reduction with current context. Also, API is now simpler-to-use I suppose.

So let me provide a new tutorial on collective signing. Like in the previous tutorial, first we pay to 2-out-of-3 spending script (with keys stored in registers):

{
val pkA  = SELF.R4[GroupElement].get
val pkB  = SELF.R5[GroupElement].get
val pkC  = SELF.R6[GroupElement].get

atLeast(2, Coll(proveDlog(pkA), proveDlog(pkB), proveDlog(pkC)))
}

Then, when a transaction is confirmed (https://explorer.ergoplatform.com/en/transactions/71aa67f95e96827193bdf711f6ccf41b30ef8bbbdaef63ed672dfb7420a4c314) , we get output bytes via /utxo/byIdBinary/{boxId} . Then we generate an unsigned transaction by providing inputs directly, in our example, by providing the following input to /wallet/transaction/generateUnsigned :

{
  "requests": [
    {
      "address": "4MQyML64GnzMxZgm",
      "value": 999000000
    }
  ],
  "fee": 1000000,
  "inputsRaw": [
"8094ebdc0310010404987300830308cde4c6a70407cde4c6a70507cde4c6a706079a8f1300030702b353df14cd94849c36194bba03000dafaeb91b3a425a863f5660565189ddfe8f070354efc32652cad6cf1231be987afa29a686af30b5735995e3ce51339c4d0ca380070235647199b150d8fab315d74e44b78866787d0330241fd471f98bf6c2bffe1e8d71aa67f95e96827193bdf711f6ccf41b30ef8bbbdaef63ed672dfb7420a4c31400"
  ],
  "dataInputsRaw": [ 
  ]
}

Then Alice generates commitments for the unsigned transaction by sending it to the NEW /wallet/generateCommitments (additional secrets to be used along with wallet's can be provided also), and in the output she's getting both secret and public hints:

{
  "secretHints": {
    "0": [
      {
        "type": "dlog",
        "a": "03c855c50d173f1b0e2797390b71d82023dcb8e12950e4fa0b9ae3be17bacca2a1",
        "pubkey": {
          "op": -51,
          "h": "02b353df14cd94849c36194bba03000dafaeb91b3a425a863f5660565189ddfe8f"
        },
        "position": "0-0",
        "hint": "cmtWithSecret",
        "secret": "..."
      }
    ]
  },
  "publicHints": {
    "0": [
      {
        "type": "dlog",
        "a": "03c855c50d173f1b0e2797390b71d82023dcb8e12950e4fa0b9ae3be17bacca2a1",
        "pubkey": {
          "op": -51,
          "h": "02b353df14cd94849c36194bba03000dafaeb91b3a425a863f5660565189ddfe8f"
        },
        "position": "0-0",
        "hint": "cmtReal"
      }
    ]
  }
}

(secret randomness is omitted to avoid private key extraction).

Then Alice must store secret hints locally and provide public to Bob. Bob is signing using Alice's hints by sending a request to /wallet/transaction/sign like:

{
  "tx": {
  "id": "6c7bf7a9720d26bec5c3b5bf1bc6199e9a5b876ba5994ab5e4214b0d8eed1492",
  "inputs": [
    {
      "boxId": "9bcbbce28b19132c28b2e088ddea03f792673e9c4509a239145c241c891ca4b9",
      "extension": {}
    }
  ],
  "dataInputs": [],
  "outputs": [
    {
      "boxId": "5bb78563af3843e5bf816c9dd50bd7c0a0b09c7fd2da2da075a8e5d8f545cb7f",
      "value": 999000000,
      "ergoTree": "10010101d17300",
      "assets": [],
      "creationHeight": 313682,
      "additionalRegisters": {},
      "transactionId": "6c7bf7a9720d26bec5c3b5bf1bc6199e9a5b876ba5994ab5e4214b0d8eed1492",
      "index": 0
    },
    {
      "boxId": "b5a1a069015f94bf7daaec46fc121044607603c844d1c6d6a8e9b2322379b375",
      "value": 1000000,
      "ergoTree": "1005040004000e36100204a00b08cd0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ea02d192a39a8cc7a701730073011001020402d19683030193a38cc7b2a57300000193c2b2a57301007473027303830108cdeeac93b1a57304",
      "assets": [],
      "creationHeight": 313682,
      "additionalRegisters": {},
      "transactionId": "6c7bf7a9720d26bec5c3b5bf1bc6199e9a5b876ba5994ab5e4214b0d8eed1492",
      "index": 1
    }
  ]
},
  "inputsRaw": [
"8094ebdc0310010404987300830308cde4c6a70407cde4c6a70507cde4c6a706079a8f1300030702b353df14cd94849c36194bba03000dafaeb91b3a425a863f5660565189ddfe8f070354efc32652cad6cf1231be987afa29a686af30b5735995e3ce51339c4d0ca380070235647199b150d8fab315d74e44b78866787d0330241fd471f98bf6c2bffe1e8d71aa67f95e96827193bdf711f6ccf41b30ef8bbbdaef63ed672dfb7420a4c31400"
  ],
  "dataInputsRaw": [

  ],
  "secrets": {

  },
  "hints": {
  "secretHints": {

  },
  "publicHints": {
    "0": [
      {
        "type": "dlog",
        "a": "03c855c50d173f1b0e2797390b71d82023dcb8e12950e4fa0b9ae3be17bacca2a1",
        "pubkey": {
          "op": -51,
          "h": "02b353df14cd94849c36194bba03000dafaeb91b3a425a863f5660565189ddfe8f"
        },
        "position": "0-0",
        "hint": "cmtReal"
      }
    ]
  }
}
}

and sending signed (but invalid) transaction to Alice (he can send hints generated on the next step instead).

Now Alice is extracting commitment from Bob and Carol from the transaction, by sending a request to /script/extractHints like:

{
  "transaction": {
  "id": "6c7bf7a9720d26bec5c3b5bf1bc6199e9a5b876ba5994ab5e4214b0d8eed1492",
  "inputs": [
    {
      "boxId": "9bcbbce28b19132c28b2e088ddea03f792673e9c4509a239145c241c891ca4b9",
      "spendingProof": {
        "proofBytes": "7d64cd47d3dc8bc5c336297e494f48d601c586175b37da228a54a77f52eb1ce307c22c7541368b73307bf37be4051406b49e989b4aee9f27789de0c426a8231fead96178544cbb54b37286f2630dcd9d5794ae9905697e8eeb0d03540d4cb8352a0734187d5e84b6d0825f12e3fcf287ee24e48d3a2a6dfb56471c41767ef88a3279e8fdc70274d85baf16686b641eaa",
        "extension": {}
      }
    }
  ],
  "dataInputs": [],
  "outputs": [
    {
      "boxId": "5bb78563af3843e5bf816c9dd50bd7c0a0b09c7fd2da2da075a8e5d8f545cb7f",
      "value": 999000000,
      "ergoTree": "10010101d17300",
      "assets": [],
      "creationHeight": 313682,
      "additionalRegisters": {},
      "transactionId": "6c7bf7a9720d26bec5c3b5bf1bc6199e9a5b876ba5994ab5e4214b0d8eed1492",
      "index": 0
    },
    {
      "boxId": "b5a1a069015f94bf7daaec46fc121044607603c844d1c6d6a8e9b2322379b375",
      "value": 1000000,
      "ergoTree": "1005040004000e36100204a00b08cd0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ea02d192a39a8cc7a701730073011001020402d19683030193a38cc7b2a57300000193c2b2a57301007473027303830108cdeeac93b1a57304",
      "assets": [],
      "creationHeight": 313682,
      "additionalRegisters": {},
      "transactionId": "6c7bf7a9720d26bec5c3b5bf1bc6199e9a5b876ba5994ab5e4214b0d8eed1492",
      "index": 1
    }
  ],
  "size": 313
},
  "real": [
    {
      "op": -51,
      "h": "0354efc32652cad6cf1231be987afa29a686af30b5735995e3ce51339c4d0ca380"
    }
  ],
  "simulated": [
    {
      "op": -51,
      "h": "0235647199b150d8fab315d74e44b78866787d0330241fd471f98bf6c2bffe1e8d"     
    }
  ]
}

and then she adds her secret hint to generate valid signed transaction, a request to /wallet/transaction/sign would be like:

{
  "tx": {
  "id": "6c7bf7a9720d26bec5c3b5bf1bc6199e9a5b876ba5994ab5e4214b0d8eed1492",
  "inputs": [
    {
      "boxId": "9bcbbce28b19132c28b2e088ddea03f792673e9c4509a239145c241c891ca4b9",
      "extension": {}
    }
  ],
  "dataInputs": [],
  "outputs": [
    {
      "boxId": "5bb78563af3843e5bf816c9dd50bd7c0a0b09c7fd2da2da075a8e5d8f545cb7f",
      "value": 999000000,
      "ergoTree": "10010101d17300",
      "assets": [],
      "creationHeight": 313682,
      "additionalRegisters": {},
      "transactionId": "6c7bf7a9720d26bec5c3b5bf1bc6199e9a5b876ba5994ab5e4214b0d8eed1492",
      "index": 0
    },
    {
      "boxId": "b5a1a069015f94bf7daaec46fc121044607603c844d1c6d6a8e9b2322379b375",
      "value": 1000000,
      "ergoTree": "1005040004000e36100204a00b08cd0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ea02d192a39a8cc7a701730073011001020402d19683030193a38cc7b2a57300000193c2b2a57301007473027303830108cdeeac93b1a57304",
      "assets": [],
      "creationHeight": 313682,
      "additionalRegisters": {},
      "transactionId": "6c7bf7a9720d26bec5c3b5bf1bc6199e9a5b876ba5994ab5e4214b0d8eed1492",
      "index": 1
    }
  ]
},
  "inputsRaw": [
"8094ebdc0310010404987300830308cde4c6a70407cde4c6a70507cde4c6a706079a8f1300030702b353df14cd94849c36194bba03000dafaeb91b3a425a863f5660565189ddfe8f070354efc32652cad6cf1231be987afa29a686af30b5735995e3ce51339c4d0ca380070235647199b150d8fab315d74e44b78866787d0330241fd471f98bf6c2bffe1e8d71aa67f95e96827193bdf711f6ccf41b30ef8bbbdaef63ed672dfb7420a4c31400"
  ],
  "dataInputsRaw": [
  ],
  "secrets": { 
  },
  "hints": {
  "secretHints": {
    "0": [
      {
        "type": "dlog",
        "a": "03c855c50d173f1b0e2797390b71d82023dcb8e12950e4fa0b9ae3be17bacca2a1",
        "pubkey": {
          "op": -51,
          "h": "02b353df14cd94849c36194bba03000dafaeb91b3a425a863f5660565189ddfe8f"
        },
        "position": "0-0",
        "hint": "cmtWithSecret",
        "secret": "...."
      }
    ]
  },
  "publicHints": {
    "0": [
      {
        "type": "dlog",
        "a": "02b6c2b73e59ad061211cebb37a7d9b238b9388cdb0c3b96ae2152ba174f67de90",
        "pubkey": {
          "op": -51,
          "h": "0235647199b150d8fab315d74e44b78866787d0330241fd471f98bf6c2bffe1e8d"
        },
        "position": "0-2",
        "hint": "cmtSimulated"
      },
      {
        "hint": "proofSimulated",
        "challenge": "e39924c725e1aee0cb705ce18a15d5425148939b7739e628",
        "pubkey": {
          "op": -51,
          "h": "0235647199b150d8fab315d74e44b78866787d0330241fd471f98bf6c2bffe1e8d"
        },
        "proof": "e39924c725e1aee0cb705ce18a15d5425148939b7739e628ee24e48d3a2a6dfb56471c41767ef88a3279e8fdc70274d85baf16686b641eaa",
        "position": "0-2"
      },
      {
        "type": "dlog",
        "a": "0323bd7f1b87280aa2b7cb2a374da1897ef7d5fae7ab3948440907d303427740ba",
        "pubkey": {
          "op": -51,
          "h": "0354efc32652cad6cf1231be987afa29a686af30b5735995e3ce51339c4d0ca380"
        },
        "position": "0-1",
        "hint": "cmtReal"
      },
      {
        "hint": "proofReal",
        "challenge": "69cd83b8770ab203ccb27094cb235e31613360e0933cf22e",
        "pubkey": {
          "op": -51,
          "h": "0354efc32652cad6cf1231be987afa29a686af30b5735995e3ce51339c4d0ca380"
        },
        "proof": "69cd83b8770ab203ccb27094cb235e31613360e0933cf22e5794ae9905697e8eeb0d03540d4cb8352a0734187d5e84b6d0825f12e3fcf287",
        "position": "0-1"
      }
    ]
  }
}
}

(secret randomness omitted again)

And now generated signed valid transaction can be broadcasted.

Taken from the forum post Improved distributed signatures ~ Sept, 2020

Let’s sign collectively! Distributed signatures API

It is well-known (among hardcore Ergonauts) that Ergo Platform has support for variety of composeable and efficient zero-knowledge proofs of knowledge in its authentication language. In particular, it provides possibility for ring and threshold signatures preserving zero-knowledge, so, for example, by observing 2-out-of-3 threshold signature associated with public keys of (Alice, Bob, Carol) on the blockchain, it is not possible to figure out who were those two parties who signed.

While Ergo Platform protocol supports threshold and ring signatures since day one, concrete applications doing signing were not available eventually. First, the ErgoMix implementations (by @anon92048 and then @anon2020s ) are using kind of ring signature hiding a spending path. And recently EIP-11 was introduced which is about adding support for distributed signing to the reference client (and not only). The implementation of EIP-11 is mostly done in this PR and in I'm going to explain how to use new API method

First, let's make a script for a box protected with 2-out-of-3 threshold signature. One option is about to hardcode public keys in the script, I've chosen another way, namely, to put public keys of signers into registers:

{
val pkA  = SELF.R4[GroupElement].get
val pkB  = SELF.R5[GroupElement].get
val pkC  = SELF.R6[GroupElement].get

atLeast(2, Coll(proveDlog(pkA), proveDlog(pkB), proveDlog(pkC)))
}

. Then we need to lock money with concrete keys. It seems the node Swagger API is the most convenient way to do that, e.g. by posting the following payment request to /wallet/payment/send:

[
    {
      "address": "umRXw8E7PmdHbswncxFe3jmE51oGFzL4CqfZYfiRr4aL",
      "value": 1000000000,    
      "registers": {
          "R4": "0702b353df14cd94849c36194bba03000dafaeb91b3a425a863f5660565189ddfe8f",
          "R5": "070354efc32652cad6cf1231be987afa29a686af30b5735995e3ce51339c4d0ca380",
          "R6": "070235647199b150d8fab315d74e44b78866787d0330241fd471f98bf6c2bffe1e8d"
      }
    }
]

(use /utils/addressToRaw/{address} method to get encoded points from P2PK addresses shown by the wallet and prepend group element type descriptor "07" then).

Thus we're getting the following box, for example (output #0): https://explorer.ergoplatform.com/en/transactions/0ffc5ba326262f25016c59c58a34b2f7fed2ea1b69f8f007d270f6ef07bced49 . We can get binary representation of unspent box via /utxo/byIdBinary API method, e.g. utxo/byIdBinary/f1892011ed31b8e51f3f3ae44f6d9132070343a7bf6db61b3342696a7873a085 gives us:

"8094ebdc0310010404987300830308cde4c6a70407cde4c6a70507cde4c6a70607abdd0f00030702b353df14cd94849c36194bba03000dafaeb91b3a425a863f5660565189ddfe8f070354efc32652cad6cf1231be987afa29a686af30b5735995e3ce51339c4d0ca380070235647199b150d8fab315d74e44b78866787d0330241fd471f98bf6c2bffe1e8d0ffc5ba326262f25016c59c58a34b2f7fed2ea1b69f8f007d270f6ef07bced4900"

We use first two keys for signing (as it is 2-out-of). Please note that members of the signing ring know (and choose) who's signing and who will be simulated.

Let's assume that the first key belongs to Alice, and the second key belongs to Bob. The the signing procedure could be as follows:

First, Alice generates secret randomness and public commitment to it by posting the following request to /script/generateCommitment :

{
  "op": -51,
  "h": "02b353df14cd94849c36194bba03000dafaeb91b3a425a863f5660565189ddfe8f"
}

result is : { "r": "...", "a": "02c103443ab26655a1ef36f44643700cc5f4730a2a1830085dda1072ccfb30940f" }

("r" is omitted to avoid private key recovery) She sends then "a" value to Bob along with unsigned transaction generated via /wallet/transaction/generateUnsigned.

Now Bob is generating invalid (partial) signature by sending the following signing request to /wallet/transaction/sign with Alice's commitment included as hint:

{
  "tx": {
  "id": "ebafdd3a00bd3d9aa26be4056c4f742e9cb0702cce9864567aa4e34cda185d54",
  "inputs": [
    {
      "boxId": "f1892011ed31b8e51f3f3ae44f6d9132070343a7bf6db61b3342696a7873a085",
      "extension": {}
    }
  ],
  "dataInputs": [],
  "outputs": [
    {
      "boxId": "49974195f916dcc95e94c72bcf6e90200bbf2c06e5cc7a22a505bad1da77927d",
      "value": 999000000,
      "ergoTree": "10010101d17300",
      "assets": [],
      "creationHeight": 257748,
      "additionalRegisters": {},
      "transactionId": "ebafdd3a00bd3d9aa26be4056c4f742e9cb0702cce9864567aa4e34cda185d54",
      "index": 0
    },
    {
      "boxId": "096192e25b691697b5afdd4a5cf39c69b84fba8b483f475c2e2d3cf47758abb1",
      "value": 1000000,
      "ergoTree": "1005040004000e36100204a00b08cd0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ea02d192a39a8cc7a701730073011001020402d19683030193a38cc7b2a57300000193c2b2a57301007473027303830108cdeeac93b1a57304",
      "assets": [],
      "creationHeight": 257748,
      "additionalRegisters": {},
      "transactionId": "ebafdd3a00bd3d9aa26be4056c4f742e9cb0702cce9864567aa4e34cda185d54",
      "index": 1
    }
  ]
},

  "secrets": {
    "dlog": [      
    ],
    "dht": [     
    ]
  },

  "hints": [
    {
      "hint": "cmtReal",
      "type": "dlog",
      "pubkey":{
         "op": -51,
         "h": "02b353df14cd94849c36194bba03000dafaeb91b3a425a863f5660565189ddfe8f"
      },
      "a": "02c103443ab26655a1ef36f44643700cc5f4730a2a1830085dda1072ccfb30940f"  
    }
  ]
}

Bob is sending transaction with invalid signature to Alice. First, she extracts Bob's and Carol's (simulated by Bob actually) commitments and partial signatures by feeding Bob's invalid transaction and Bob's and Carol's pubkeys into /script/extractHints :

{
  "transaction": {
  "id": "ebafdd3a00bd3d9aa26be4056c4f742e9cb0702cce9864567aa4e34cda185d54",
  "inputs": [
    {
      "boxId": "f1892011ed31b8e51f3f3ae44f6d9132070343a7bf6db61b3342696a7873a085",
      "spendingProof": {
        "proofBytes": "2c429642fba49717349dbeafb5c4a0725d012e901352fdcc390d53103824fcadb82553ddef404d69794679327c976bc3195dc78926c8161437d595a0a04dac8d3fe08d999d3c70f18c92cddaa1260d3a564c7431c478825d52bd28673637d986f545e871bd07a99cf6c3e04c2342da40f4608fb322f213c2b1f9166ad526cb7d7a931b02b8966f6ba4cc98aa435156f4",
        "extension": {}
      }
    }
  ],
  "dataInputs": [],
  "outputs": [
    {
      "boxId": "49974195f916dcc95e94c72bcf6e90200bbf2c06e5cc7a22a505bad1da77927d",
      "value": 999000000,
      "ergoTree": "10010101d17300",
      "assets": [],
      "creationHeight": 257748,
      "additionalRegisters": {},
      "transactionId": "ebafdd3a00bd3d9aa26be4056c4f742e9cb0702cce9864567aa4e34cda185d54",
      "index": 0
    },
    {
      "boxId": "096192e25b691697b5afdd4a5cf39c69b84fba8b483f475c2e2d3cf47758abb1",
      "value": 1000000,
      "ergoTree": "1005040004000e36100204a00b08cd0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ea02d192a39a8cc7a701730073011001020402d19683030193a38cc7b2a57300000193c2b2a57301007473027303830108cdeeac93b1a57304",
      "assets": [],
      "creationHeight": 257748,
      "additionalRegisters": {},
      "transactionId": "ebafdd3a00bd3d9aa26be4056c4f742e9cb0702cce9864567aa4e34cda185d54",
      "index": 1
    }
  ],
  "size": 313
},
  "real": [
    {
      "op": -51,
      "h": "0354efc32652cad6cf1231be987afa29a686af30b5735995e3ce51339c4d0ca380"
    }
  ],
  "simulated": [
    {
      "op": -51,
      "h": "0235647199b150d8fab315d74e44b78866787d0330241fd471f98bf6c2bffe1e8d"     
    }
  ]
}

and then she adds a hint with her commitment and randomness and produces transaction. A request to /wallet/transaction/sign is as follows:

{
  "tx": {
  "id": "ebafdd3a00bd3d9aa26be4056c4f742e9cb0702cce9864567aa4e34cda185d54",
  "inputs": [
    {
      "boxId": "f1892011ed31b8e51f3f3ae44f6d9132070343a7bf6db61b3342696a7873a085",
      "extension": {}
    }
  ],
  "dataInputs": [],
  "outputs": [
    {
      "boxId": "49974195f916dcc95e94c72bcf6e90200bbf2c06e5cc7a22a505bad1da77927d",
      "value": 999000000,
      "ergoTree": "10010101d17300",
      "assets": [],
      "creationHeight": 257748,
      "additionalRegisters": {},
      "transactionId": "ebafdd3a00bd3d9aa26be4056c4f742e9cb0702cce9864567aa4e34cda185d54",
      "index": 0
    },
    {
      "boxId": "096192e25b691697b5afdd4a5cf39c69b84fba8b483f475c2e2d3cf47758abb1",
      "value": 1000000,
      "ergoTree": "1005040004000e36100204a00b08cd0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ea02d192a39a8cc7a701730073011001020402d19683030193a38cc7b2a57300000193c2b2a57301007473027303830108cdeeac93b1a57304",
      "assets": [],
      "creationHeight": 257748,
      "additionalRegisters": {},
      "transactionId": "ebafdd3a00bd3d9aa26be4056c4f742e9cb0702cce9864567aa4e34cda185d54",
      "index": 1
    }
  ]
},


  "hints": [
    {
      "hint": "cmtWithSecret",
      "type": "dlog",
      "pubkey": {
         "op": -51,
         "h": "02b353df14cd94849c36194bba03000dafaeb91b3a425a863f5660565189ddfe8f"
      },
      "a": "02c103443ab26655a1ef36f44643700cc5f4730a2a1830085dda1072ccfb30940f" ,
      "secret": "..." 
    },
     {
    "type": "dlog",
    "a": "035e5bcc7edcf47454fc41bf4b824e7357cb1b6a95df89c79d10cf867713d2d5ac",
    "pubkey": {
      "op": -51,
      "h": "0235647199b150d8fab315d74e44b78866787d0330241fd471f98bf6c2bffe1e8d"
    },
    "hint": "cmtSimulated"
  },
  {
    "hint": "proofSimulated",
    "challenge": "e0556372b3c893e1fdf34bc8850577c9d6cba5c697eb4189",
    "pubkey": {
      "op": -51,
      "h": "0235647199b150d8fab315d74e44b78866787d0330241fd471f98bf6c2bffe1e8d"
    },
    "proof": "e0556372b3c893e1fdf34bc8850577c9d6cba5c697eb4189f4608fb322f213c2b1f9166ad526cb7d7a931b02b8966f6ba4cc98aa435156f4"
  },
  {
    "type": "dlog",
    "a": "0277755feea497330c74d6442dfe9895e8de037a6b4e0d96e555b13f0f17d8869c",
    "pubkey": {
      "op": -51,
      "h": "0354efc32652cad6cf1231be987afa29a686af30b5735995e3ce51339c4d0ca380"
    },
    "hint": "cmtReal"
  },
  {
    "hint": "proofReal",
    "challenge": "d95830628bec6f4c45d618156a453aa0af8ddcf4eb7c2a4a",
    "pubkey": {
      "op": -51,
      "h": "0354efc32652cad6cf1231be987afa29a686af30b5735995e3ce51339c4d0ca380"
    },
    "proof": "d95830628bec6f4c45d618156a453aa0af8ddcf4eb7c2a4a564c7431c478825d52bd28673637d986f545e871bd07a99cf6c3e04c2342da40"
  }
  ]
}

("secret" aka "r" skipped again)

As result, we're getting JSON-encoded transaction which can be broadcasted via POST request to /transactions to get the transaction on the blockchain.

Still, the code is not finalized ("op": -51 looks ugly, however, it was done previously) and not user-friendly, so as a next step, some applications working with the API are needed. - Let’s sign collectively! Distributed signatures API ~ June, 2020