|
1 | 1 | //define name and version
|
2 | 2 | const appname = "cardano-signer"
|
3 |
| -const version = "1.17.0" |
| 3 | +const version = "1.18.0" |
4 | 4 |
|
5 | 5 | //external dependencies
|
6 | 6 | const CardanoWasm = require("@emurgo/cardano-serialization-lib-nodejs")
|
@@ -142,6 +142,20 @@ switch (topic) {
|
142 | 142 | console.log(``)
|
143 | 143 | break;
|
144 | 144 |
|
| 145 | + case 'verify-cip100': |
| 146 | + console.log(``) |
| 147 | + console.log(`${Bright}${Underscore}Verify Signatures in CIP-100 governance JSON-LD metadata:${Reset}`) |
| 148 | + console.log(``) |
| 149 | + console.log(` Syntax: ${Bright}${appname} ${FgGreen}verify --cip100${Reset}`); |
| 150 | + console.log(` Params: ${FgGreen}--data${Reset} "<jsonld-text>" | ${FgGreen}--data-file${Reset} "<path_to_jsonld_file>"${Reset}`); |
| 151 | + console.log(` ${Dim}data or file in jsonld format to verify${Reset}`); |
| 152 | + console.log(` [${FgGreen}--json${Reset} |${FgGreen} --json-extended${Reset}] ${Dim}optional flag to generate output in json/json-extended format${Reset}`); |
| 153 | + console.log(` [${FgGreen}--out-file${Reset} "<path_to_file>"] ${Dim}path to an output file, default: standard-output${Reset}`); |
| 154 | + console.log(` Output: ${FgCyan}"true/false"${Reset} or ${FgCyan}JSON-Format${Reset}`); |
| 155 | + console.log(``) |
| 156 | + break; |
| 157 | + |
| 158 | + |
145 | 159 | case 'keygen':
|
146 | 160 | case 'keygen-cip36':
|
147 | 161 | console.log(``)
|
@@ -185,6 +199,7 @@ switch (topic) {
|
185 | 199 | showUsage('sign-cip36',false)
|
186 | 200 | showUsage('verify',false)
|
187 | 201 | showUsage('verify-cip8',false)
|
| 202 | + showUsage('verify-cip100',false) |
188 | 203 | showUsage('keygen',false)
|
189 | 204 | showUsage('hash-cip100',false)
|
190 | 205 | console.log(``)
|
@@ -1688,6 +1703,122 @@ async function main() {
|
1688 | 1703 |
|
1689 | 1704 | break;
|
1690 | 1705 |
|
| 1706 | + case "verify-cip100": //CANONIZE AND HASH JSONLD GOVERNANCE METADATA, CHECKS ALL THE AUTHORS SIGNATURES |
| 1707 | + |
| 1708 | + //load default variable |
| 1709 | + var result = false //will be true if all authors signatures are valid/verified and no error occurs |
| 1710 | + var errorStr = '' //will hold an explanation about an error |
| 1711 | + var authors_array = [] //holds the authors for the output with there name and verified field |
| 1712 | + |
| 1713 | + //lets try to load data from the data parameter |
| 1714 | + var data = args['data']; |
| 1715 | + if ( typeof data === 'undefined' || data == '' ) { |
| 1716 | + |
| 1717 | + //no data parameter present, lets try the data-file parameter |
| 1718 | + var data_file = args['data-file']; |
| 1719 | + if ( typeof data_file === 'undefined' || data_file == '' ) {console.error(`Error: Missing data / data-file to hash`); showUsage(workMode);} |
| 1720 | + |
| 1721 | + //data-file present lets try to read and parse the file |
| 1722 | + try { |
| 1723 | + var jsonld_input = JSON.parse(fs.readFileSync(data_file,'utf8')); //parse the given key as a json file |
| 1724 | + } catch (error) { console.error(`Error: Can't read data-file '${data_file}' or not valid JSON-LD data`); process.exit(1); } |
| 1725 | + |
| 1726 | + } else { |
| 1727 | + //data parameter present, lets see if its valid json data |
| 1728 | + try { |
| 1729 | + var jsonld_input = JSON.parse(data); |
| 1730 | + } catch (error) { console.error(`Error: Not valid JSON data (${error})`); process.exit(1); } |
| 1731 | + } |
| 1732 | + |
| 1733 | + //JSON data is loaded into jsonld_input, now lets only use the @context and body key |
| 1734 | + try { |
| 1735 | + if ( jsonld_input["body"] === undefined || jsonld_input["@context"] === undefined ) { console.error(`Error: JSON-LD must contain '@context' and 'body' data`); process.exit(1); } |
| 1736 | + var jsonld_data = { "body" : jsonld_input["body"], "@context": jsonld_input["@context"] }; // var jsonld_data = {}; jsonld_data["body"] = jsonld_doc["body"]; jsonld_data["@context"] = jsonld_doc["@context"]; |
| 1737 | + } catch (error) { console.error(`Error: Couldn't extract '@context' and 'body' JSON-LD data (${error})`); process.exit(1); } |
| 1738 | + |
| 1739 | + //Start the async canonize process, will get triggered via the .then part once the process finished |
| 1740 | + jsonld.canonize(jsonld_data, {safe: false, algorithm: 'URDNA2015', format: 'application/n-quads'}).then( (canonized_data) => |
| 1741 | + { |
| 1742 | + //data was successfully canonized |
| 1743 | + |
| 1744 | + //get the hash of the canonized data |
| 1745 | + if (jsonld_input["hashAlgorithm"] != 'blake2b-256') { console.error(`Error: unknown hashAlgorithm ${jsonld_input["hashAlgorithm"]}`); process.exit(1); } |
| 1746 | + var canonized_hash = getHash(Buffer.from(canonized_data).toString('hex')); |
| 1747 | + |
| 1748 | + //do all the testing now in the verifyAuthors block |
| 1749 | + verifyAuthors: { |
| 1750 | + |
| 1751 | + //check that the authors entry is present , if not break with an errordescription |
| 1752 | + if ( jsonld_input["authors"] === undefined ) { errorStr='missing authors entry'; break verifyAuthors; } |
| 1753 | + var jsonld_authors = jsonld_input["authors"]; |
| 1754 | + //check that the authors entry is an array |
| 1755 | + if ( typeof jsonld_authors !== 'object' || jsonld_authors instanceof Array == false ) { errorStr='authors entry is not an array'; break verifyAuthors; } |
| 1756 | + //check that the number of authors is not zero |
| 1757 | + if ( jsonld_authors.length == 0) { errorStr='no authors in the authors-array'; break verifyAuthors; } |
| 1758 | + //check each authors array entry |
| 1759 | + jsonld_authors.every( authorEntry => { |
| 1760 | + var authorName = authorEntry["name"]; if (typeof authorName !== 'string') { errorStr='authors.name entry is not an string'; return false; } |
| 1761 | + var authorWitness = authorEntry["witness"]; if (typeof authorWitness !== 'object') { errorStr='authors.witness entry is missing or not a json object'; return false; } |
| 1762 | + var authorWitnessAlgorithm = authorWitness["witnessAlgorithm"]; if (authorWitnessAlgorithm != 'ed25519') { errorStr=`authors.witness.algorithm entry for '${authorName}' is missing or not 'ed25519'`; return false; } |
| 1763 | + |
| 1764 | + var authorWitnessPublicKey = authorWitness["publicKey"]; if (typeof authorWitnessPublicKey !== 'string' || ! regExpHex.test(authorWitnessPublicKey) ) { errorStr=`authors.witness.publickey entry for '${authorName}' is not a valid hex-string`; return false; } |
| 1765 | + //load the public key |
| 1766 | + try { |
| 1767 | + var publicKey = CardanoWasm.PublicKey.from_bytes(Buffer.from(authorWitnessPublicKey,'hex')); |
| 1768 | + } catch (error) { errorStr=`authors.witness.publickey entry for '${authorName}' error ${error}`; return false; } |
| 1769 | + |
| 1770 | + var authorWitnessSignature = authorWitness["signature"]; if (typeof authorWitnessSignature !== 'string' || ! regExpHex.test(authorWitnessSignature) ) { errorStr=`authors.witness.signature entry for '${authorName}' is not a valid hex-string`; return false; } |
| 1771 | + |
| 1772 | + //load the Ed25519Signature |
| 1773 | + try { |
| 1774 | + var ed25519signature = CardanoWasm.Ed25519Signature.from_hex(authorWitnessSignature); |
| 1775 | + } catch (error) { errorStr=`authors.witness.signature entry for '${authorName}' error ${error}`; return false; } |
| 1776 | + |
| 1777 | + //do the verification |
| 1778 | + var verified = publicKey.verify(Buffer.from(canonized_hash,'hex'),ed25519signature); |
| 1779 | + if (!verified) { errorStr=`at least one invalid signature found`; } |
| 1780 | + |
| 1781 | + //add it to the array of authors |
| 1782 | + var authorArrayEntry = { "name" : authorName, "publicKey" : authorWitnessPublicKey, "signature" : authorWitnessSignature, "valid" : verified }; |
| 1783 | + authors_array.push(authorArrayEntry); |
| 1784 | + |
| 1785 | + //return=true -> go to the next entry |
| 1786 | + return true; |
| 1787 | + }) |
| 1788 | + |
| 1789 | + } |
| 1790 | + |
| 1791 | + if ( errorStr == '' ) { result = true; } |
| 1792 | + |
| 1793 | + //compose the content for the output |
| 1794 | + if ( args['json'] === true ) { //generate content in json format |
| 1795 | + var content = `{ "result": ${result}, "errorMsg": "${errorStr}", "authors": ` + JSON.stringify(authors_array) + ` }`; |
| 1796 | + } else if ( args['json-extended'] === true ) { //generate content in extended json format |
| 1797 | + //split the canonized data into an array, remove the last element, do a loop for each element |
| 1798 | + var canonized_array = []; |
| 1799 | + canonized_data.split('\n').slice(0,-1).forEach( (element) => { |
| 1800 | + canonized_array.push('"' + String(element).replace(/[\"]/g, '\\"') + '"'); //replace the " with a \" while pushing new elements to the array |
| 1801 | + }) |
| 1802 | + var content = `{ "workMode": "${workMode}", "result": ${result}, "errorMsg": "${errorStr}", "authors": ` + JSON.stringify(authors_array) + `, "hash": "${canonized_hash}", "body": ` + JSON.stringify(jsonld_data["body"]) + `, "canonized": [ ${canonized_array} ] }`; |
| 1803 | + } else { //generate content in text format |
| 1804 | + var content = result; |
| 1805 | + } |
| 1806 | + |
| 1807 | + //output the content to the console or to a file |
| 1808 | + var out_file = args['out-file']; |
| 1809 | + //if there is no --out-file parameter specified or the parameter alone (true) then output to the console |
| 1810 | + if ( typeof out_file === 'undefined' || out_file == '' ) { console.log(content);} //Output to console |
| 1811 | + else { //else try to write the content out to the given file |
| 1812 | + try { |
| 1813 | + fs.writeFileSync(out_file,content, 'utf8') |
| 1814 | + // file written successfully |
| 1815 | + } catch (error) { console.error(`${error}`); process.exit(1); } |
| 1816 | + } |
| 1817 | + |
| 1818 | + }).catch( (err) => {console.log(`Error: Could not canonize the data (${err.message})`);process.exit(1);}); |
| 1819 | + |
| 1820 | + break; |
| 1821 | + |
1691 | 1822 |
|
1692 | 1823 | default:
|
1693 | 1824 | //if workMode is not found, exit with and errormessage and showUsage
|
|
0 commit comments