diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..090a1f0
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+.idea
+.DS_Store
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..31949b5
--- /dev/null
+++ b/README.md
@@ -0,0 +1,268 @@
+# Tool to sign data with a Cardano-Secret-Key // verify data with a Cardano-Public-Key // generate CIP-8 & CIP-36 data
+
+
+
+### What can cardano-signer sign?
+* **Sign** any hexdata, textdata or binaryfile with a provided normal or extended secret key. The key can be provided in hex, bech or file format. The signing output is a signature in hex format and also the public key of the provided secret key for verification.
+* Sign payloads in **CIP-8** mode. The signing output is a signature in hex format and also the public key of the provided secret key for verification.
+* Generate and sign **Catalyst registration/delegation** metadata cbor in **CIP-36** mode. This also includes relatively weighted voting power delegation. The output is the registration/delegation data in cbor hex format or a binary cbor file, which can be transmitted on chain as it is.
+
+### What can cardano-signer verify?
+* **Verify** a signature for any hexdata, textdata or binaryfile together with a provided public key. The key can be provided in hex, bech or file format. The verification output is true(exitcode=0) or false(exitcode=1).
+
+
+
+
+## Usage
+
+``` console
+
+$ ./cardano-signer help
+
+cardano-signer 1.6.0
+
+Signing a hex/text-string or a binary-file:
+
+ Syntax: cardano-signer sign
+ Params: --data-hex "" | --data "" | --data-file ""
+ data/payload/file to sign in hexformat or textformat
+ --secret-key "||" path to a signing-key-file or a direct signing hex/bech-key string
+ [--out-file ""] path to an output file, default: standard-output
+ Output: signature_hex + publicKey_hex
+
+
+Signing a payload in CIP-8 mode:
+
+ Syntax: cardano-signer sign --cip8
+ Params: --data-hex "" | --data "" | --data-file ""
+ data/payload/file to sign in hexformat or textformat
+ --secret-key "||" path to a signing-key-file or a direct signing hex/bech-key string
+ --address "" signing address (bech format like 'stake1_...')
+ [--out-file ""] path to an output file, default: standard-output
+ Output: signature_hex + publicKey_hex
+
+
+Signing a catalyst registration/delegation in CIP-36 mode:
+
+ Syntax: cardano-signer sign --cip36
+ Params: --vote-public-key "||" public-key-file or public hex/bech-key string to delegate the votingpower to
+ --vote-weight relative weight of the delegated votingpower, default: 1 (=100% for single delegation)
+ --secret-key "||" signing-key-file or a direct signing hex/bech-key string of the stake key (votingpower)
+ --rewards-address "" rewards stake address (bech format like 'stake1_...')
+ --nonce nonce value, this is typically the slotheight(tip) of the chain
+ [--vote-purpose ] optional parameter (unsigned int), default: 0 (catalyst)
+ [--out-file ""] path to write a binary metadata.cbor file to
+ Output: registration_data_cbor_hex
+
+
+Verifying a hex/text-string or a binary-file(data) via signature + publicKey:
+
+ Syntax: cardano-signer verify
+ Params: --data-hex "" | --data "" | --data-file ""
+ data/payload/file to verify in hexformat or textformat
+ --signature "" signature in hexformat
+ --public-key "||" path to a public-key-file or a direct public hex/bech-key string
+ Output: true(exitcode 0) or false(exitcode 1)
+
+```
+
+
+
+
+## Examples
+
+### Signing (defaultmode)
+
+``` console
+### SIGN HEXDATA OR TEXTDATA WITH A KEY-HEXSTRING
+
+$ cardano-signer sign \
+ --data-hex "8f21b675423a65244483506122492f5720d7bd35d70616348089678ed4eb07a9" \
+ --secret-key "c14ef0cc5e352446d6243976f51e8ffb2ae257f2a547c4fba170964a76501e7a"
+
+ca3ddc10f845dbe0c22875aaf91f66323d3f28e265696dcd3c56b91a8e675c9e30fd86ba69b9d1cf271a12f7710c9f3385c78cbf016e17e1df339bea8bd2db03 9be513df12b3fabe7c1b8c3f9fab0968eb2168d5689bf981c2f7c35b11718b27
+
+$ cardano-signer sign \
+ --out-file mySignature.txt \
+ --data-hex "8f21b675423a65244483506122492f5720d7bd35d70616348089678ed4eb07a9" \
+ --secret-key "c14ef0cc5e352446d6243976f51e8ffb2ae257f2a547c4fba170964a76501e7a"
+#Signature+publicKey was written to the file mySignature.txt
+
+$ cardano-signer sign \
+ --data-hex "8f21b675423a65244483506122492f5720d7bd35d70616348089678ed4eb07a9" \
+ --secret-key "c14ef0cc5e352446d6243976f51e8ffb2ae257f2a547c4fba170964a"
+Error: Invalid normal secret key
+
+$ cardano-signer sign \
+ --data-hex "8f21b675423a65244483506122492f5720d7bd35d70616348089678ed4eb07a9" \
+ --secret-key "c14ef0cc5e352446d6243976f51e8ffb2ae257f2a547c4fba170964a76501e7a88afe88fa8f888544e6f5a5f555e5faf6f6f"
+Error: Invalid extended secret key
+
+### SIGN HEXDATA OR TEXTDATA WITH A KEY-FILE
+
+$ cardano-signer sign \
+ --data-hex "8f21b675423a65244483506122492f5720d7bd35d70616348089678ed4eb07a9" \
+ --secret-key owner.staking.skey
+
+ca3ddc10f845dbe0c22875aaf91f66323d3f28e265696dcd3c56b91a8e675c9e30fd86ba69b9d1cf271a12f7710c9f3385c78cbf016e17e1df339bea8bd2db03 9be513df12b3fabe7c1b8c3f9fab0968eb2168d5689bf981c2f7c35b11718b27
+
+$ cardano-signer sign \
+ --data-hex "8f21b675423a65244483506122492f5720d7bd35d70616348089678ed4eb07a9" \
+ --secret-key owner.staking.vkey
+Error: The file 'owner.staking.vkey' is not a signing/secret key json
+
+### SIGN A FILE WITH A KEY-FILE
+
+$ cardano-signer sign --data-file test.txt --secret-key test.skey
+
+caacb18c46319f55b932efa77357f14b66b27aa908750df2c91800dc59711015ea2e568974ac0bcabf9b1c4708b877c2b94a7658c2dcad78b108049062572e09 57758911253f6b31df2a87c10eb08a2c9b8450768cb8dd0d378d93f7c2e220f0
+
+```
+
+
+
+### Signing (CIP-8 mode)
+
+``` console
+### SIGN TEXTDATA IN CIP-8 MODE
+
+$ cardano-signer sign --cip8 \
+ --address "stake_test1uqt3nqapz799tvp2lt8adttt29k6xa2xnltahn655tu4sgc6asaqg" \
+ --data '{"choice":"Yes","comment":"","network":"preview","proposal":"2038c417d112e005ef61c95d710ee62184a6c177d18b2da891f97cefae4f8535","protocol":"SundaeSwap","title":"Test Proposal - Tampered","version":"1","votedAt":"3137227","voter":"stake_test1uqt3nqapz799tvp2lt8adttt29k6xa2xnltahn655tu4sgc6asaqg"}' \
+ --secret-key myStakeKey.skey
+
+5b2e7ac3fbe3cec1540f98fcc29c1ab63778e14a653a2328b2e56af6fd2a714540708e5f3e19670b9b867151c7dfb75061c6b94508d88f43ad3b3893ca213506 57758911253f6b31df2a87c10eb08a2c9b8450768cb8dd0d378d93f7c2e220f0
+
+### SIGN HEXDATA IN CIP-8 MODE
+
+$ cardano-signer sign --cip8 \
+ --address "stake_test1uqt3nqapz799tvp2lt8adttt29k6xa2xnltahn655tu4sgc6asaqg" \
+ --data-hex "7b2263686f696365223a22596573222c22636f6d6d656e74223a22222c226e6574776f726b223a2270726576696577222c2270726f706f73616c223a2232303338633431376431313265303035656636316339356437313065653632313834613663313737643138623264613839316639376365666165346638353335222c2270726f746f636f6c223a2253756e64616553776170222c227469746c65223a22546573742050726f706f73616c202d2054616d7065726564222c2276657273696f6e223a2231222c22766f7465644174223a2233313337323237222c22766f746572223a227374616b655f7465737431757174336e7161707a373939747670326c7438616474747432396b36786132786e6c7461686e363535747534736763366173617167227d" \
+ --secret-key myStakeKey.skey
+
+5b2e7ac3fbe3cec1540f98fcc29c1ab63778e14a653a2328b2e56af6fd2a714540708e5f3e19670b9b867151c7dfb75061c6b94508d88f43ad3b3893ca213506 57758911253f6b31df2a87c10eb08a2c9b8450768cb8dd0d378d93f7c2e220f0
+```
+
+
+
+### Signing (CIP-36 mode) - Catalyst Voting Registration / VotingPower Delegation
+
+``` console
+### REGISTER/DELEGATE TO A SINGLE VOTING-KEY
+
+$ cardano-signer sign --cip36 \
+ --rewards-address "stake_test1urqntq4wexjylnrdnp97qq79qkxxvrsa9lcnwr7ckjd6w0cr04y4p" \
+ --secret-key ../owner.staking.skey \
+ --vote-public-key somevote.vkey \
+ --nonce 71948552 \
+ --out-file catalyst-delegation.cbor
+
+a219ef64a5018182582057758911253f6b31df2a87c10eb08a2c9b8450768cb8dd0d378d93f7c2e220f0010258209be513df12b3fabe7c1b8c3f9fab0968eb2168d5689bf981c2f7c35b11718b2703581de0c13582aec9a44fcc6d984be003c5058c660e1d2ff1370fd8b49ba73f041a0449d908050019ef65a1015840c839244556db17a2df914c7291c891e5abd1bd580de7786d640da9e27983efe86495cbee900eb685c08e367e778bb0860c6e366b9ec715d8fba824ef55c8aa0f
+
+### REGISTER/DELEGATE TO MULTIPLE VOTING-KEYS WITH VOTINGPOWER 10%,20%,70%
+
+$ cardano-signer sign --cip36 \
+ --rewards-address "stake_test1urqntq4wexjylnrdnp97qq79qkxxvrsa9lcnwr7ckjd6w0cr04y4p" \
+ --secret-key ../owner.staking.skey \
+ --vote-public-key ../somevote.vkey \
+ --vote-weight 10 \
+ --vote-public-key "C2CD50D8A231FBC1444D65ABAB4F6BF74178E6DE64722558EEEF0B73DE293A8A" \
+ --vote-weight 20 \
+ --vote-public-key "ed25519_pk128c305nw9xh20kearuhcwj447kzlvxdfttkk6uwnrf6qfjm9276svd678w" \
+ --vote-weight 70 \
+ --nonce 71948552 \
+ --out-file catalyst-multidelegation.cbor
+
+a219ef64a5018382582099d1d0c4cdc8a4b206066e9606c6c3729678bd7338a8eab9bffdffa39d3df9580a825820c2cd50d8a231fbc1444d65abab4f6bf74178e6de64722558eeef0b73de293a8a1482582051f117d26e29aea7db3d1f2f874ab5f585f619a95aed6d71d31a7404cb6557b518460258209be513df12b3fabe7c1b8c3f9fab0968eb2168d5689bf981c2f7c35b11718b2703581de0c13582aec9a44fcc6d984be003c5058c660e1d2ff1370fd8b49ba73f041a0449d908050019ef65a1015840ecce4b2e10146857b9f583ce01b10a26726022963d47fd61d0fbb67b543428fa46315d4e35b2ab73e7e15f620883176422a19e780a751d71ac488053365e6402
+
+```
+
+
+
+### Verification (defaultmode)
+
+``` console
+### VERIFY HEXDATA or TEXTDATA WITH A SIGNATURE AND A KEY-HEXSTRING
+
+$ cardano-signer verify \
+ --data-hex "8f21b675423a65244483506122492f5720d7bd35d70616348089678ed4eb07a9" \
+ --signature "ca3ddc10f845dbe0c22875aaf91f66323d3f28e265696dcd3c56b91a8e675c9e30fd86ba69b9d1cf271a12f7710c9f3385c78cbf016e17e1df339bea8bd2db03" \
+ --public-key "9be513df12b3fabe7c1b8c3f9fab0968eb2168d5689bf981c2f7c35b11718b27"
+
+true
+
+$ cardano-signer verify \
+ --data-hex "8f21b675423a65244483506122492f5720d7bd35d70616348089678ed4eb07a9" \
+ --signature "ca3ddc10f845dbe0c22875aaf91f66323d3f28e265696dcd3c56b91a8e675c9e30fd86ba69b9d1cf271a12f7710c9f3385c78cbf016e17e1df339bea8bd2db03" \
+ --public-key "aaaaaaaaaab3fabe7c1b8c3f9fab0968eb2168d5689bf981c2f7c35b11718b27"
+
+false
+
+$ cardano-signer verify \
+ --data-hex "8f21b675423a65244483506122492f5720d7bd35d70616348089678ed4eb07a9" \
+ --signature "aaaaaaaaaa45dbe0c22875aaf91f66323d3f28e265696dcd3c56b91a8e675c9e30fd86ba69b9d1cf271a12f7710c9f3385c78cbf016e17e1df339bea8bd2db03" \
+ --public-key "9be513df12b3fabe7c1b8c3f9fab0968eb2168d5689bf981c2f7c35b11718b27"
+
+false
+
+### VERIFY HEXDATA WITH A SIGNATURE AND A KEY-FILE
+
+$ cardano-signer verify \
+ --data-hex "8f21b675423a65244483506122492f5720d7bd35d70616348089678ed4eb07a9" \
+ --signature "ca3ddc10f845dbe0c22875aaf91f66323d3f28e265696dcd3c56b91a8e675c9e30fd86ba69b9d1cf271a12f7710c9f3385c78cbf016e17e1df339bea8bd2db03" \
+ --public-key owner.staking.vkey
+
+true
+
+$ cardano-signer verify \
+ --data-hex "8f21b675423a65244483506122492f5720d7bd35d70616348089678ed4eb07a9" \
+ --signature "ca3ddc10f845dbe0c22875aaf91f66323d3f28e265696dcd3c56b91a8e675c9e30fd86ba69b9d1cf271a12f7710c9f3385c78cbf016e17e1df339bea8bd2db03" \
+ --public-key owner.staking.skey
+Error: The file 'owner.staking.skey' is not a verification/public key json
+
+### VERIFY A FILE WITH A SIGNATURE AND A KEY-FILE
+
+$ cardano-signer verify --data-file test.txt --public-key test.vkey --signature "caacb18c46319f55b932efa77357f14b66b27aa908750df2c91800dc59711015ea2e568974ac0bcabf9b1c4708b877c2b94a7658c2dcad78b108049062572e09"
+
+true
+```
+
+
+
+
+## Release Notes
+
+* **1.6.0**
+ - New Syntax - Now you can use the parameter `--data-file` to use any binary file as the data source to sign.
+ - Added the function to directly use bech encoded secret and public keys for the signing/verification. You can mix the formats.
+
+* **1.5.0**
+ - New CIP-36 mode via parameter `--cip36`. This enables the new catalyst/governance registration and votingpower (multi-)delegation mode. Output generates a signed cbor file or hex_string.
+
+* **1.4.0**
+ - New CIP-8 mode via parameter `--cip8`. This enables CIP-8 conform payload signing.
+ - New Syntax - Now you can use the parameter `--data` for pure text payloads, and `--data-hex` for hex-encoded payloads.
+
+* **1.3.0**
+ - Now supporting true parameter/flag names.
+ - Added new optional `--out-file` option, which would write the signature+publicKey to a file and not to the standard output.
+
+* **1.2.0**
+ - Added support to use Cardano-Key-Files in addition to a direct Key-Hexstring. Supports standard sKey/vKey JSON files and also files with a Bech32-Key in it, like the ones generated via jcli
+
+* **1.1.0**
+ - Added functionality to do also a Verification of the Signature together with the data and the Public Key.
+
+* **1.0.0**
+ - Initial version, supports signing of a Data-Hexstring string with a Key-Hexstring.
+
+
+
+
+## Contacts
+
+* Telegram - @atada_stakepool
+* Twitter - [@ATADA_Stakepool](https://twitter.com/ATADA_Stakepool)
+* Discord - MartinLang \[ATADA, SPO Scripts\]#5306
+* Email - stakepool@stakepool.at
+* Homepage - https://stakepool.at
diff --git a/cardano-signer-1.6.0_linux-x64.tar.gz b/cardano-signer-1.6.0_linux-x64.tar.gz
new file mode 100644
index 0000000..6d8ac2b
Binary files /dev/null and b/cardano-signer-1.6.0_linux-x64.tar.gz differ
diff --git a/cardano-signer-1.6.0_mac-x64.tar.gz b/cardano-signer-1.6.0_mac-x64.tar.gz
new file mode 100644
index 0000000..fc5f252
Binary files /dev/null and b/cardano-signer-1.6.0_mac-x64.tar.gz differ
diff --git a/cardano-signer-1.6.0_windows-x64.tar.gz b/cardano-signer-1.6.0_windows-x64.tar.gz
new file mode 100644
index 0000000..d30aae2
Binary files /dev/null and b/cardano-signer-1.6.0_windows-x64.tar.gz differ
diff --git a/src/cardano-signer.js b/src/cardano-signer.js
new file mode 100644
index 0000000..097f7de
--- /dev/null
+++ b/src/cardano-signer.js
@@ -0,0 +1,572 @@
+const appname = "cardano-signer"
+const version = "1.6.0"
+
+const CardanoWasm = require("@emurgo/cardano-serialization-lib-nodejs")
+const cbor = require("cbor");
+const fs = require("fs");
+const blake2 = require('blake2');
+const args = require('minimist')(process.argv.slice(2));
+
+const regExp = /^[0-9a-fA-F]+$/;
+
+//catch all exceptions that are not catched via try
+process.on('uncaughtException', function (error) {
+ console.error(`${error}`); process.exit(1);
+});
+
+
+
+function showUsage(){
+ console.log(``)
+ console.log(`Signing a hex/text-string or a binary-file:`)
+ console.log(``)
+ console.log(` Syntax: ${appname} sign`);
+ console.log(` Params: --data-hex "" | --data "" | --data-file ""`);
+ console.log(` data/payload/file to sign in hexformat or textformat`);
+ console.log(` --secret-key "||" path to a signing-key-file or a direct signing hex/bech-key string`);
+ console.log(` [--out-file ""] path to an output file, default: standard-output`);
+ console.log(` Output: signature_hex + publicKey_hex`);
+ console.log(``)
+ console.log(``)
+ console.log(`Signing a payload in CIP-8 mode:`)
+ console.log(``)
+ console.log(` Syntax: ${appname} sign --cip8`);
+ console.log(` Params: --data-hex "" | --data "" | --data-file ""`);
+ console.log(` data/payload/file to sign in hexformat or textformat`);
+ console.log(` --secret-key "||" path to a signing-key-file or a direct signing hex/bech-key string`);
+ console.log(` --address "" signing address (bech format like 'stake1_...')`);
+ console.log(` [--out-file ""] path to an output file, default: standard-output`);
+ console.log(` Output: signature_hex + publicKey_hex`);
+ console.log(``)
+ console.log(``)
+ console.log(`Signing a catalyst registration/delegation in CIP-36 mode:`)
+ console.log(``)
+ console.log(` Syntax: ${appname} sign --cip36`);
+ console.log(` Params: --vote-public-key "||" public-key-file or public hex/bech-key string to delegate the votingpower to`);
+ console.log(` --vote-weight relative weight of the delegated votingpower, default: 1 (=100% for single delegation)`);
+ console.log(` --secret-key "||" signing-key-file or a direct signing hex/bech-key string of the stake key (votingpower)`);
+ console.log(` --rewards-address "" rewards stake address (bech format like 'stake1_...')`);
+ console.log(` --nonce nonce value, this is typically the slotheight(tip) of the chain`);
+ console.log(` [--vote-purpose ] optional parameter (unsigned int), default: 0 (catalyst)`);
+ console.log(` [--out-file ""] path to write a binary metadata.cbor file to`);
+ console.log(` Output: registration_data_cbor_hex`);
+ console.log(``)
+ console.log(``)
+ console.log(`Verifying a hex/text-string or a binary-file(data) via signature + publicKey:`)
+ console.log(``)
+ console.log(` Syntax: ${appname} verify`);
+ console.log(` Params: --data-hex "" | --data "" | --data-file ""`);
+ console.log(` data/payload/file to verify in hexformat or textformat`);
+ console.log(` --signature "" signature in hexformat`);
+ console.log(` --public-key "||" path to a public-key-file or a direct public hex/bech-key string`);
+ console.log(` Output: true(exitcode 0) or false(exitcode 1)`)
+ console.log(``)
+ console.log(``)
+ console.log(`Info:`);
+ console.log(` https://github.com/gitmachtl (Cardano SPO Scripts \/\/ ATADA Stakepools Austria)`)
+ console.log(``)
+ process.exit(1);
+}
+
+
+function trimString(s){
+ s = s.replace(/(^\s*)|(\s*$)/gi,""); //exclude start and end white-space
+ s = s.replace(/\n /,"\n"); // exclude newline with a start spacing
+ return s;
+}
+
+
+function readKey2hex(key,type) { //reads a standard-cardano-skey/vkey-file-json or a direct hex entry // returns a hexstring of the key
+
+ var key_hex = "";
+
+ switch (type) {
+
+ case "secret": //convert a secret key into a hex string
+
+ // try to use the parameter as a filename for a cardano skey json with a cborHex entry
+ try {
+ const key_json = JSON.parse(fs.readFileSync(key,'utf8')); //parse the given key as a json file
+ const is_singing_key = key_json.type.toLowerCase().includes('signing') //boolean if the json contains the keyword 'signing' in the type field
+ if ( ! is_singing_key ) { console.error(`Error: The file '${key}' is not a signing/secret key json`); process.exit(1); }
+ key_hex = key_json.cborHex.substring(4).toLowerCase(); //cut off the leading "5820/5840" from the cborHex
+ //check that the given key is a hex string
+ if ( ! regExp.test(key_hex) ) { console.error(`Error: The secret key in file '${key}' entry 'cborHex' is not a valid hex string`); process.exit(1); }
+ return key_hex;
+ } catch (error) {}
+
+ // try to use the parameter as a filename for a bech encoded string in it (typical keyfiles generated via jcli)
+ try {
+ const content = trimString(fs.readFileSync(key,'utf8')); //read the content of the given key from a file
+ try { //try to load it as a bech secret key
+ const tmp_key = CardanoWasm.PrivateKey.from_bech32(content); //temporary key to check about bech32 format
+ key_hex = Buffer.from(tmp_key.as_bytes()).toString('hex');
+ } catch (error) { console.error(`Error: The content in file '${key}' is not a valid bech secret key`); process.exit(1); }
+ return key_hex;
+ } catch (error) {}
+
+ // try to use the parameter as a bech encoded string
+ try {
+ const tmp_key = CardanoWasm.PrivateKey.from_bech32(key); //temporary key to check about bech32 format
+ key_hex = Buffer.from(tmp_key.as_bytes()).toString('hex');
+ return key_hex;
+ } catch (error) {}
+
+ // try to use the parameter as a direct hex string
+ key_hex = trimString(key.toLowerCase());
+ //check that the given key is a hex string
+ if ( ! regExp.test(key) ) { console.error(`Error: Provided secret key '${key}' is not a valid secret key. Or not a hex string, bech encoded key, or the file is missing`); process.exit(1); }
+ return key_hex;
+ break;
+
+
+ case "public": //convert a public key into a hex string
+
+ // try to use the parameter as a filename for a cardano vkey json with a cborHex entry
+ try {
+ const key_json = JSON.parse(fs.readFileSync(key,'utf8')); //parse the given key as a json file
+ const is_verification_key = key_json.type.toLowerCase().includes('verification') //boolean if the json contains the keyword 'verification' in the type field
+ if ( ! is_verification_key ) { console.error(`Error: The file '${key}' is not a verification/public key json`); process.exit(1); }
+ key_hex = key_json.cborHex.substring(4).toLowerCase(); //cut off the leading "5820/5840" from the cborHex
+ //check that the given key is a hex string
+ if ( ! regExp.test(key_hex) ) { console.error(`Error: The public key in file '${key}' entry 'cborHex' is not a valid hex string`); process.exit(1); }
+ return key_hex;
+ } catch (error) {}
+
+ // try to use the parameter as a filename for a bech encoded string in it (typical keyfiles generated via jcli)
+ try {
+ const content = trimString(fs.readFileSync(key,'utf8')); //read the content of the given key from a file
+ try { //try to load it as a bech public key
+ const tmp_key = CardanoWasm.PublicKey.from_bech32(content); //temporary key to check about bech32 format
+ key_hex = Buffer.from(tmp_key.as_bytes()).toString('hex');
+ } catch (error) { console.error(`Error: The content in file '${key}' is not a valid bech public key`); process.exit(1); }
+ return key_hex;
+ } catch (error) {}
+
+ // try to use the parameter as a bech encoded string
+ try {
+ const tmp_key = CardanoWasm.PublicKey.from_bech32(key); //temporary key to check about bech32 format
+ key_hex = Buffer.from(tmp_key.as_bytes()).toString('hex');
+ return key_hex;
+ } catch (error) {}
+
+ // try to use the parameter as a direct hex string
+ key_hex = trimString(key.toLowerCase());
+ //check that the given key is a hex string
+ if ( ! regExp.test(key) ) { console.error(`Error: Provided public key '${key}' is not a valid public key. Or not a hex string, bech encoded key, or the file is missing`); process.exit(1); }
+ return key_hex;
+ break;
+
+ } //switch (type)
+
+}
+
+function getHash(content) { //hashes a given hex-string content with blake2b_256 (digestLength 32)
+ const h = blake2.createHash("blake2b", { digestLength: 32 });
+ h.update(Buffer.from(content, 'hex'));
+ return h.digest("hex")
+}
+
+// MAIN
+//
+// first parameter -> workMode: sign or verify
+//
+// workMode: sign (defaultmode without flags)
+// --data / --data-hex -> textdata / hexdata that should be signed
+// --secret-key -> signing key in hex/bech/file format
+// --out-file -> signed data in hex format + public key in hex format
+//
+// workMode: sign --cip8 FLAG
+// --data / --data-hex -> textdata / hexdata that should be signed
+// --secret-key -> signing key in hex/bech/file format
+// --address -> signing address
+// --out-file -> signed data in hex format + public key in hex format
+//
+// workMode: sign --cip36 FLAG
+// --vote-public-key -> public key in hex/bech/file format of the voting public key (one or multiple)
+// --vote-weight -> relative voting weight (one or multiple)
+// --secret-key -> signing key in hex/bech/file format
+// --rewards-address -> rewards stake address
+// --nonce -> nonce, typically the slotheight(tip) of the chain
+// --vote-purpose -> optional unsigned_int parameter, default: 0 (catalyst)
+// --out-file -> binary metadata.cbor file
+//
+// workMode: verify (defaultmode without flags)
+// --data / --data-hex -> textdata / hexdata that should be verified
+// --signature -> signed data(signature) in hex format for verification
+// --public-key -> public key for verification in hex/bech/file format
+// output -> true (exitcode 0) or false (exitcode 1)
+//
+
+async function main() {
+
+ //show help or usage if no parameter is provided
+ if ( ! process.argv[2] || process.argv[2].toLowerCase().includes('help') ) { console.log(`${appname} ${version}`); showUsage(); }
+
+ //show version
+ if ( process.argv[2].toLowerCase().includes('version') ) { console.log(`${appname} ${version}`); process.exit(0); }
+
+ //first paramter - workMode: "sign or verify"
+ var workMode = process.argv[2];
+ if ( ! workMode ) { showUsage(); }
+ workMode = trimString(workMode.toLowerCase());
+
+ //CIP8-Flag-Check
+ const cip8_flag = args['cip8'];
+ if ( cip8_flag === true ) {workMode = workMode + '-cip8'}
+
+ //CIP36-Flag-Check
+ const cip36_flag = args['cip36'];
+ if ( cip36_flag === true ) {workMode = workMode + '-cip36'}
+
+ //choose the workmode
+ switch (workMode) {
+
+ case "sign": //SIGN DATA IN DEFAULT MODE
+
+ //get data-hex to sign -> store it in sign_data_hex
+ var sign_data_hex = args['data-hex'];
+ if ( typeof sign_data_hex === 'undefined' || sign_data_hex === true ) {
+
+ //no data-hex parameter present, lets try the data parameter
+ var sign_data = args['data'];
+ if ( typeof sign_data === 'undefined' || sign_data === true ) {
+
+ //no data parameter present, lets try the data-file parameter
+ var sign_data_file = args['data-file'];
+ if ( typeof sign_data_file === 'undefined' || sign_data_file === true ) {console.error(`Error: Missing data / data-hex / data-file to sign`); showUsage();}
+
+ //data-file present lets read the file and store it hex encoded in sign_data_hex
+ try {
+ sign_data_hex = fs.readFileSync(sign_data_file,null).toString('hex'); //reads the file as binary
+ } catch (error) { console.log(`Error: Can't read data-file '${sign_data_file}'`); process.exit(1); }
+
+ } else {
+ //data parameter present, lets convert it to hex and store it in the sign_data_hex variable
+ sign_data_hex = Buffer.from(sign_data).toString('hex');
+ }
+
+ }
+ sign_data_hex = trimString(sign_data_hex.toLowerCase());
+
+ //check that the given data is a hex string
+ if ( ! regExp.test(sign_data_hex) ) { console.error(`Error: Data to sign is not a valid hex string`); showUsage(); }
+
+ //get signing key -> store it in sign_key
+ var key_file_hex = args['secret-key'];
+ if ( typeof key_file_hex === 'undefined' || key_file_hex === true ) { console.error(`Error: Missing secret key parameter`); showUsage(); }
+
+ //read in the key from a file or direct hex
+ sign_key = readKey2hex(key_file_hex, 'secret');
+
+ //load the private key (normal or extended)
+ try {
+ if ( sign_key.length <= 64 ) { var prvKey = CardanoWasm.PrivateKey.from_normal_bytes(Buffer.from(sign_key, "hex")); }
+ else { var prvKey = CardanoWasm.PrivateKey.from_extended_bytes(Buffer.from(sign_key.substring(0,128), "hex")); } //use only the first 64 bytes (128 chars)
+ } catch (error) { console.log(`Error: ${error}`); process.exit(1); }
+
+ //generate the public key from the secret key for external verification
+ var pubKey = Buffer.from(prvKey.to_public().as_bytes()).toString('hex')
+
+ //sign the data
+ try {
+ var signedBytes = prvKey.sign(Buffer.from(sign_data_hex, 'hex')).to_bytes();
+ var signature = Buffer.from(signedBytes).toString('hex');
+ } catch (error) { console.error(`Error: ${error}`); process.exit(1); }
+
+ //output the signature data and the public key
+ var content = signature + " " + pubKey;
+ var out_file = args['out-file'];
+ //if there is no --out-file parameter specified or the parameter alone (true) then output to the console
+ if ( typeof out_file === 'undefined' || out_file === true ) { console.log(content); }
+ else { //else try to write the content out to the given file
+ try {
+ const outFile = fs.createWriteStream(out_file);
+ outFile.write(content, 'utf8');
+ outFile.end();
+ // file written successfully
+ } catch (error) { console.error(`${error}`); process.exit(1); }
+ }
+ break;
+
+
+ case "sign-cip8": //SIGN DATA IN CIP-8 MODE
+
+ //get data-hex to sign -> store it in sign_data_hex
+ var sign_data_hex = args['data-hex'];
+ if ( typeof sign_data_hex === 'undefined' || sign_data_hex === true ) {
+
+ //no data-hex parameter present, lets try the data parameter
+ var sign_data = args['data'];
+ if ( typeof sign_data === 'undefined' || sign_data === true ) {
+
+ //no data parameter present, lets try the data-file parameter
+ var sign_data_file = args['data-file'];
+ if ( typeof sign_data_file === 'undefined' || sign_data_file === true ) {console.error(`Error: Missing data / data-hex / data-file to sign`); showUsage();}
+
+ //data-file present lets read the file and store it hex encoded in sign_data_hex
+ try {
+ sign_data_hex = fs.readFileSync(sign_data_file,null).toString('hex'); //reads the file as binary
+ } catch (error) { console.log(`Error: Can't read data-file '${sign_data_file}'`); process.exit(1); }
+
+ } else {
+ //data parameter present, lets convert it to hex and store it in the sign_data_hex variable
+ sign_data_hex = Buffer.from(sign_data).toString('hex');
+ }
+
+ }
+ sign_data_hex = trimString(sign_data_hex.toLowerCase());
+
+ //check that the given data is a hex string
+ if ( ! regExp.test(sign_data_hex) ) { console.error(`Error: Data to sign is not a valid hex string`); showUsage(); }
+
+ //get signing address (stake or paymentaddress in bech format)
+ var sign_addr = args['address'];
+ if ( typeof sign_addr === 'undefined' || sign_addr === true ) { console.error(`Error: Missing CIP-8 signing address (bech-format)`); showUsage(); }
+ sign_addr = trimString(sign_addr.toLowerCase());
+ try {
+ var sign_addr_hex = CardanoWasm.Address.from_bech32(sign_addr).to_hex();
+ } catch (error) { console.error(`Error: The CIP-8 signing address '${sign_addr}' is not a valid bech address`); process.exit(1); }
+
+ //generate the Signature1 inner cbor (single signing key)
+ const signature1_cbor = Buffer.from(cbor.encode(new Map().set(1,-8).set('address',Buffer.from(sign_addr_hex,'hex')))).toString('hex')
+
+ //generate the data to sign cbor -> overwrites the current sign_data_hex variable at the end
+ const sign_data_array = [ "Signature1", Buffer.from(signature1_cbor,'hex'),Buffer.from(''), Buffer.from(sign_data_hex,'hex') ]
+
+ //overwrite the sign_data_hex with the cbor encoded sign_data_array
+ sign_data_hex = cbor.encode(sign_data_array).toString('hex');
+
+ //get signing key -> store it in sign_key
+ var key_file_hex = args['secret-key'];
+ if ( typeof key_file_hex === 'undefined' || key_file_hex === true ) { console.error(`Error: Missing secret key parameter`); showUsage(); }
+
+ //read in the key from a file or direct hex
+ sign_key = readKey2hex(key_file_hex, 'secret');
+
+ //load the private key (normal or extended)
+ try {
+ if ( sign_key.length <= 64 ) { var prvKey = CardanoWasm.PrivateKey.from_normal_bytes(Buffer.from(sign_key, "hex")); }
+ else { var prvKey = CardanoWasm.PrivateKey.from_extended_bytes(Buffer.from(sign_key.substring(0,128), "hex")); } //use only the first 64 bytes (128 chars)
+ } catch (error) { console.log(`Error: ${error}`); process.exit(1); }
+
+ //generate the public key from the secret key for external verification
+ var pubKey = Buffer.from(prvKey.to_public().as_bytes()).toString('hex')
+
+ //sign the data
+ try {
+ var signedBytes = prvKey.sign(Buffer.from(sign_data_hex, 'hex')).to_bytes();
+ var signature = Buffer.from(signedBytes).toString('hex');
+ } catch (error) { console.error(`Error: ${error}`); process.exit(1); }
+
+ //output the signature data and the public key
+ var content = signature + " " + pubKey;
+ var out_file = args['out-file'];
+ //if there is no --out-file parameter specified or the parameter alone (true) then output to the console
+ if ( typeof out_file === 'undefined' || out_file === true ) { console.log(content); }
+ else { //else try to write the content out to the given file
+ try {
+ const outFile = fs.createWriteStream(out_file);
+ outFile.write(content, 'utf8');
+ outFile.end();
+ // file written successfully
+ } catch (error) { console.error(`${error}`); process.exit(1); }
+ }
+ break;
+
+
+ case "sign-cip36": //SIGN DATA IN CIP-36 MODE (Catalyst)
+
+ //get rewards stakeaddress in bech format
+ var rewards_addr = args['rewards-address'];
+ if ( typeof rewards_addr === 'undefined' || rewards_addr === true ) { console.error(`Error: Missing rewards stake address (bech-format)`); process.exit(1); }
+ rewards_addr = trimString(rewards_addr.toLowerCase());
+ if ( rewards_addr.substring(0,5) != 'stake' ) { console.error(`Error: The rewards stake address '${rewards_addr}' is not a stake address`); process.exit(1); }
+ try {
+ var rewards_addr_hex = CardanoWasm.Address.from_bech32(rewards_addr).to_hex();
+ } catch (error) { console.error(`Error: The rewards stake address '${rewards_addr}' is not a valid bech address`); process.exit(1); }
+
+ //get signing key -> store it in sign_key
+ var key_file_hex = args['secret-key'];
+ if ( typeof key_file_hex === 'undefined' || key_file_hex === true ) { console.error(`Error: Missing secret key parameter`); process.exit(1); }
+
+ //read in the key from a file or direct hex
+ sign_key = readKey2hex(key_file_hex, 'secret');
+
+ //load the private key (normal or extended)
+ try {
+ if ( sign_key.length <= 64 ) { var prvKey = CardanoWasm.PrivateKey.from_normal_bytes(Buffer.from(sign_key, "hex")); }
+ else { var prvKey = CardanoWasm.PrivateKey.from_extended_bytes(Buffer.from(sign_key.substring(0,128), "hex")); } //use only the first 64 bytes (128 chars)
+ } catch (error) { console.log(`Error: ${error}`); process.exit(1); }
+
+ //generate the public key from the secret key for external verification
+ var pubKey = Buffer.from(prvKey.to_public().as_bytes()).toString('hex')
+
+ //get deleg vote public key(s) -> store it in vote_public_key
+ var vote_public_key = args['vote-public-key'];
+ if ( typeof vote_public_key === 'undefined' || vote_public_key === true ) { console.error(`Error: Missing vote public key(s) parameter`); process.exit(1); }
+
+ //if there is only one --vote-public-key parameter present, convert it to an array
+ if ( typeof vote_public_key === 'string' ) { vote_public_key = [ vote_public_key ]; }
+ if ( typeof vote_public_key === 'number' || vote_public_key === true ) { console.error(`Error: You've provided a number as a public key`); process.exit(1); }
+
+ //get deleg voting weight -> store it in vote_weight
+ var vote_weight = args['vote-weight'];
+ if ( typeof vote_weight === 'undefined' ) { vote_weight = 1 }
+ if ( vote_weight === true ) { console.error(`Error: Please specify a --vote-weight parameter with an unsigned integer value > 0`); process.exit(1); }
+
+ //if there is only one --vote-weight parameter present, convert it to an array
+ if ( typeof vote_weight === 'number' ) { vote_weight = [ vote_weight ]; }
+
+ //if not the same amounts of vote_public_keys and vote_weights provided, show an error
+ if ( vote_public_key.length != vote_weight.length ) { console.error(`Error: Not the same count of --vote-public-key(` + vote_public_key.length + `) and --vote-weight(` + vote_weight.length + `) parameters`); process.exit(1); }
+
+ //build the vote_delegation array
+ const vote_delegation_array = [];
+ for (let cnt = 0; cnt < vote_public_key.length; cnt++) {
+ entry_vote_public_key = vote_public_key[cnt]
+ if ( typeof entry_vote_public_key === 'number' || entry_vote_public_key === true ) { console.error(`Error: Invalid public key parameter found, please use a filename or a hex string`); process.exit(1); }
+ entry_vote_public_key_hex = readKey2hex(entry_vote_public_key, 'public');
+ entry_vote_weight = vote_weight[cnt] + 0;
+ if (typeof entry_vote_weight !== 'number' || entry_vote_weight <= 0) { console.error(`Error: Please specify a --vote-weight parameter with an unsigned integer value > 0`); process.exit(1); }
+ vote_delegation_array.push([Buffer.from(entry_vote_public_key_hex.substring(0,64),'hex'),entry_vote_weight]) //during the push, only use the first 32bytes (64chars) of the public_key_hex
+ }
+
+ //get the --nonce parameter
+ var nonce = args['nonce'];
+ if ( typeof nonce !== 'number' || nonce === true ) { console.error(`Error: Please specify a --nonce parameter with an unsigned integer value`); process.exit(1); }
+
+ //get the --vote-purpose parameter, set default = 0
+ var vote_purpose_param = args['vote-purpose'];
+
+ if ( typeof vote_purpose_param === 'undefined' ) { vote_purpose = 0 } //if not defined, set it to default=0
+ else if ( typeof vote_purpose_param === 'number' && vote_purpose_param >= 0 ) { vote_purpose = vote_purpose_param }
+ else { console.error(`Error: Please specify a --vote-purpose parameter with an unsigned integer value`); process.exit(1); }
+
+ /*
+ build the delegation map
+ 61284: {
+ // delegations - CBOR byte array(s) of the voting_public_keys and the relative voting_weight
+ 1: [["0xa6a3c0447aeb9cc54cf6422ba32b294e5e1c3ef6d782f2acff4a70694c4d1663", 1], ["0x00588e8e1d18cba576a4d35758069fe94e53f638b6faf7c07b8abd2bc5c5cdee", 3]],
+ // stake_pub - CBOR byte array
+ 2: "0xad4b948699193634a39dd56f779a2951a24779ad52aa7916f6912b8ec4702cee",
+ // reward_address - CBOR byte array
+ 3: "0x00588e8e1d18cba576a4d35758069fe94e53f638b6faf7c07b8abd2bc5c5cdee47b60edc7772855324c85033c638364214cbfc6627889f81c4",
+ // nonce
+ 4: 5479467
+ // voting_purpose: 0 = Catalyst
+ 5: 0
+ }
+ */
+ const delegationMap = new Map().set(61284,new Map().set(1,vote_delegation_array).set(2,Buffer.from(pubKey,'hex')).set(3,Buffer.from(rewards_addr_hex,'hex')).set(4,nonce).set(5,vote_purpose));
+
+ //convert it to a cbor hex string
+ const delegationCBOR = Buffer.from(cbor.encode(delegationMap)).toString('hex');
+
+ //hash the delegationCBOR hex string
+ sign_data_hex = getHash(delegationCBOR);
+
+ //sign the data
+ try {
+ var signedBytes = prvKey.sign(Buffer.from(sign_data_hex, 'hex')).to_bytes();
+ var signature = Buffer.from(signedBytes).toString('hex');
+ } catch (error) { console.error(`Error: ${error}`); process.exit(1); }
+
+ //build the full registration map by adding the root key 61285 and the signature in key 1 below that
+ const registrationMap = delegationMap.set(61285,new Map().set(1,Buffer.from(signature,'hex')))
+
+ //convert it to a cbor hex string
+ const registrationCBOR = Buffer.from(cbor.encode(registrationMap)).toString('hex');
+
+ //output the registrationCBOR or write it to a binary file
+ var out_file = args['out-file'];
+ //if there is no --out-file parameter specified or the parameter alone (true) then output to the console
+ if ( typeof out_file === 'undefined' || out_file === true ) { console.log(registrationCBOR); }
+ else { //else try to write the content out to the given file
+ try {
+ var writeBuf = Buffer.from(registrationCBOR,'hex')
+ fs.writeFileSync(out_file, writeBuf, 'binary')
+ } catch (error) { console.error(`${error}`); process.exit(1); }
+ }
+ break;
+
+
+ case "verify": //VERIFY DATA IN DEFAULT MODE
+
+ //get data-hex to verify -> store it in verify_data_hex
+ var verify_data_hex = args['data-hex'];
+ if ( typeof verify_data_hex === 'undefined' || verify_data_hex === true ) {
+
+ //no data-hex parameter present, lets try the data parameter
+ var verify_data = args['data'];
+ if ( typeof verify_data === 'undefined' || verify_data === true ) {
+
+ //no data parameter present, lets try the data-file parameter
+ var verify_data_file = args['data-file'];
+ if ( typeof verify_data_file === 'undefined' || verify_data_file === true ) {console.error(`Error: Missing data / data-hex / data-file to verify`); showUsage();}
+
+ //data-file present lets read the file and store it hex encoded in verify_data_hex
+ try {
+ verify_data_hex = fs.readFileSync(verify_data_file,null).toString('hex'); //reads the file as binary
+ } catch (error) { console.log(`Error: Can't read data-file '${verify_data_file}'`); process.exit(1); }
+
+ } else {
+ //data parameter present, lets convert it to hex and store it in the verify_data_hex variable
+ verify_data_hex = Buffer.from(verify_data).toString('hex');
+ }
+
+ }
+ verify_data_hex = trimString(verify_data_hex.toLowerCase());
+
+ //check that the given data is a hex string
+ if ( ! regExp.test(verify_data_hex) ) { console.error(`Error: Data to verify is not a valid hex string`); process.exit(1); }
+
+ //get the signature to verify -> store it in signature
+ var signature = args['signature'];
+ if ( typeof signature === 'undefined' || signature === true ) { console.error(`Error: Missing signature`); showUsage(); }
+ signature = trimString(signature.toLowerCase());
+
+ //check that the given signature is a hex string
+ if ( ! regExp.test(signature) ) { console.error(`Error: Signature is not a valid hex string`); process.exit(1); }
+
+ //get public key -> store it in public_key
+ var key_file_hex = args['public-key'];
+ if ( typeof key_file_hex === 'undefined' || key_file_hex === true ) { console.error(`Error: Missing public key parameter`); showUsage(); }
+
+ //read in the key from a file or direct hex
+ public_key = readKey2hex(key_file_hex, 'public');
+
+ //load the public key
+ try {
+ var publicKey = CardanoWasm.PublicKey.from_bytes(Buffer.from(public_key.substring(0,64),'hex')); //only use the first 32 bytes (64 chars)
+ } catch (error) { console.error(`Error: ${error}`); process.exit(1); }
+
+ //load the Ed25519Signature
+ try {
+ var ed25519signature = CardanoWasm.Ed25519Signature.from_hex(signature);
+ } catch (error) { console.error(`Error: ${error}`); process.exit(1); }
+
+ //do the verification
+ const verified = publicKey.verify(Buffer.from(verify_data_hex,'hex'),ed25519signature);
+
+ //output the result and exit with the right exitcode
+ if ( verified ) { console.log(`true`); process.exit(0); }
+ else { console.log(`false`); process.exit(1); }
+
+ break;
+
+
+ default:
+ //if workMode is not found, exit with and errormessage and showUsage
+ console.error(`Error: Unsupported command '${workMode}'`);
+ showUsage();
+
+ } //switch
+
+}
+
+main();
+
+process.exit(0); //we're finished, exit with errorcode 0 (all good)
+
+
diff --git a/src/package.json b/src/package.json
new file mode 100644
index 0000000..de802ae
--- /dev/null
+++ b/src/package.json
@@ -0,0 +1,21 @@
+{
+ "name": "cardano-signer",
+ "version": "1.6.0",
+ "description": "cardano-signer signs a given data(hex/text/file) with a signing key(hex/bech/file) or verify the signature via a public key(hex/bech/file). also it can produce a cip-8 and cip-36 conform payload signing.",
+ "main": "cardano-signer.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "author": "@gitmachtl - ATADA Stakepools Austria",
+ "license": "MIT",
+ "dependencies": {
+ "@emurgo/cardano-serialization-lib-nodejs": "^11.0.5",
+ "blake2": "^4.1.1",
+ "cbor": "^8.1.0",
+ "minimist": "^1.2.6"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/gitmachtl/cardano-related-stuff.git"
+ }
+}