From c49daa3e55ca539d3aaa64f0b7679eee58ea5778 Mon Sep 17 00:00:00 2001 From: Harlan T Wood and Noah Thorp Date: Mon, 5 Dec 2016 18:56:47 -0800 Subject: [PATCH 1/3] Add JSON-LD verification via BTC public key --- playground/index.html | 25 ++++++++---- playground/jsonld-signatures.js | 44 ++++++++++++--------- playground/playground.css | 4 ++ playground/playground.js | 70 ++++++++++++++++++++++++++------- 4 files changed, 103 insertions(+), 40 deletions(-) diff --git a/playground/index.html b/playground/index.html index 5ef3e9a44..b67c95ac5 100755 --- a/playground/index.html +++ b/playground/index.html @@ -251,17 +251,26 @@

-
-

Bitcoin Private Key (secp256k1)

- +
+

Bitcoin Private Key (ECDSA Koblitz)

+ + +

Bitcoin Public Key (ECDSA Koblitz)

+ + +
+
+
+

NOTE: A remote context that is not known to be fully working yet was detected in your input. If you wish, you can use an alternative context created by the JSON-LD community to @@ -328,7 +337,7 @@

Bitcoin Private Key (secp256k1)

  • - + Signed with Bitcoin @@ -357,8 +366,8 @@

    Bitcoin Private Key (secp256k1)

    -
    - +
    +
  • @@ -406,7 +415,7 @@

    Bitcoin Private Key (secp256k1)

    $('#context-div').hide(); $('#frame-div').hide(); $('#privatekey-rsa-div').hide(); - $('#privatekey-secp256k1-div').hide(); + $('#privatekey-koblitz-div').hide(); $('#markup,#context,#frame').bind('keyup', function() { $('.btn-group > .btn').each(function () { $(this).removeClass('active'); diff --git a/playground/jsonld-signatures.js b/playground/jsonld-signatures.js index 76181a555..5b713f6ef 100644 --- a/playground/jsonld-signatures.js +++ b/playground/jsonld-signatures.js @@ -68,7 +68,7 @@ var libs = {}; api.SECURITY_CONTEXT_URL = 'https://w3id.org/security/v1'; api.SUPPORTED_ALGORITHMS = [ - 'BitcoinSignature2016', + 'EcdsaKoblitzSignature2016', 'GraphSignature2012', 'LinkedDataSignature2015' ]; @@ -144,11 +144,12 @@ api.sign = function(input, options, callback) { if(api.SUPPORTED_ALGORITHMS.indexOf(algorithm) === -1) { return callback(new Error( - '[jsig.sign] options.algorithm must be one of: ' + + '[jsigs.sign] Unsupported algorithm "' + algorithm + '"; ' + + 'options.algorithm must be one of: ' + JSON.stringify(api.SUPPORTED_ALGORITHMS))); } - if(algorithm === 'BitcoinSignature2016') { + if(algorithm === 'EcdsaKoblitzSignature2016') { if(typeof privateKeyWif !== 'string') { return callback(new TypeError( '[jsig.sign] options.privateKeyWif must be a base 58 formatted string.')); @@ -297,10 +298,12 @@ api.verify = function(input, options, callback) { return callback(new Error('[jsigs.verify] No signature found.')); } var algorithm = jsonld.getValues(signature, 'type')[0] || ''; + algorithm = algorithm.replace(/^.+:/, ''); // strip off any namespace to compare to known algorithm names if(api.SUPPORTED_ALGORITHMS.indexOf(algorithm) === -1) { return callback(new Error( - '[jsigs.verify] Unsupported signature algorithm; supported ' + - 'algorithms are: ' + JSON.stringify(api.SUPPORTED_ALGORITHMS))); + '[jsigs.verify] Unsupported signature algorithm "' + algorithm + '"; ' + + 'supported algorithms are: ' + + JSON.stringify(api.SUPPORTED_ALGORITHMS))); } return _verify(algorithm, input, options, callback); }); @@ -678,13 +681,14 @@ function _verify(algorithm, input, options, callback) { * @param callback(err, signature) called once the operation completes. */ var _createSignature = function(input, options, callback) { - if(options.algorithm === 'BitcoinSignature2016') { + var signature, privateKey; + + if(options.algorithm === 'EcdsaKoblitzSignature2016') { // works same in any environment - var signature; try { var bitcoreMessage = api.use('bitcoreMessage'); var bitcore = bitcoreMessage.Bitcore; - var privateKey = bitcore.PrivateKey.fromWIF(options.privateKeyWif); + privateKey = bitcore.PrivateKey.fromWIF(options.privateKeyWif); var message = bitcoreMessage(_getDataToHash(input, options)); signature = message.sign(privateKey); } catch(err) { @@ -695,7 +699,6 @@ var _createSignature = function(input, options, callback) { if(_nodejs) { // optimize using node libraries - var signature; try { var crypto = api.use('crypto'); var signer = crypto.createSign('RSA-SHA256'); @@ -708,10 +711,9 @@ var _createSignature = function(input, options, callback) { } // browser or other environment - var signature; try { var forge = api.use('forge'); - var privateKey = forge.pki.privateKeyFromPem(options.privateKeyPem); + privateKey = forge.pki.privateKeyFromPem(options.privateKeyPem); var md = forge.md.sha256.create(); md.update(_getDataToHash(input, options), 'utf8'); signature = forge.util.encode64(privateKey.sign(md)); @@ -736,12 +738,18 @@ var _createSignature = function(input, options, callback) { * @param callback(err, valid) called once the operation completes. */ var _verifySignature = function(input, signature, options, callback) { - if(options.algorithm === 'BitcoinSignature2016') { + var verified; + + if(options.algorithm === 'EcdsaKoblitzSignature2016') { // works same in any environment - var bitcoreMessage = api.use('bitcoreMessage'); - var message = bitcoreMessage(_getDataToHash(input, options)); - var verified = message.verify(options.publicKeyWif, signature); - return callback(null, verified); + try { + var bitcoreMessage = api.use('bitcoreMessage'); + var message = bitcoreMessage(_getDataToHash(input, options)); + verified = message.verify(options.publicKeyWif, signature); + return callback(null, verified); + } catch (err) { + return callback(err); + } } if(_nodejs) { @@ -749,7 +757,7 @@ var _verifySignature = function(input, signature, options, callback) { var crypto = api.use('crypto'); var verifier = crypto.createVerify('RSA-SHA256'); verifier.update(_getDataToHash(input, options), 'utf8'); - var verified = verifier.verify(options.publicKeyPem, signature, 'base64'); + verified = verifier.verify(options.publicKeyPem, signature, 'base64'); return callback(null, verified); } @@ -758,7 +766,7 @@ var _verifySignature = function(input, signature, options, callback) { var publicKey = forge.pki.publicKeyFromPem(options.publicKeyPem); var md = forge.md.sha256.create(); md.update(_getDataToHash(input, options), 'utf8'); - var verified = publicKey.verify( + verified = publicKey.verify( md.digest().bytes(), forge.util.decode64(signature)); callback(null, verified); }; diff --git a/playground/playground.css b/playground/playground.css index ab7d5def0..46b7f9ab1 100644 --- a/playground/playground.css +++ b/playground/playground.css @@ -73,3 +73,7 @@ #markup-container, #output-container { margin-top: 1em; } + +#privatekey-koblitz-div .CodeMirror { + height: 100px; +} diff --git a/playground/playground.js b/playground/playground.js index d57348569..afc522ba7 100755 --- a/playground/playground.js +++ b/playground/playground.js @@ -383,7 +383,8 @@ var editor; // don't use JSON-LD for PEM data - if(key === 'privatekey-rsa' || key === 'privatekey-secp256k1') { + if(key === 'privatekey-rsa' || key === 'privatekey-koblitz' || + key === 'publickey-koblitz') { editor = CodeMirror.fromTextArea(node, { matchBrackets: true, autoCloseBrackets: true, @@ -739,10 +740,10 @@ var id = playground.activeTab = evt.target.id; if(['tab-compacted', 'tab-flattened', 'tab-framed', - 'tab-signed-rsa', 'tab-signed-secp256k1', ].indexOf(id) > -1) { + 'tab-signed-rsa', 'tab-signed-koblitz', ].indexOf(id) > -1) { // these options require more UI inputs, so compress UI space $('#markup-div').removeClass('span12').addClass('span6'); - $('#frame-div, #privatekey-rsa-div, #privatekey-secp256k1-div, ' + + $('#frame-div, #privatekey-rsa-div, #privatekey-koblitz-div, ' + '#context-div').hide(); if(id === 'tab-compacted' || id === 'tab-flattened') { $('#param-type').html('JSON-LD Context'); @@ -753,15 +754,15 @@ } else if(id === 'tab-signed-rsa') { $('#param-type').html('PEM-encoded Private Key'); $('#privatekey-rsa-div').show(); - } else if(id === 'tab-signed-secp256k1') { + } else if(id === 'tab-signed-koblitz') { $('#param-type').html('Base 58 Encoded Private Key'); - $('#privatekey-secp256k1-div').show(); + $('#privatekey-koblitz-div').show(); } } else { // else no input textarea required $('#context-div, #frame-div, #privatekey-rsa-div, ' + - '#privatekey-secp256k1-div').hide(); + '#privatekey-koblitz-div').hide(); $('#markup-div').removeClass('span6').addClass('span12'); $('#param-type').html(''); } @@ -838,11 +839,11 @@ creator: 'https://example.com/jdoe/keys/1' }); } - else if(playground.activeTab === 'tab-signed-secp256k1') { + else if(playground.activeTab === 'tab-signed-koblitz') { options.format = 'application/ld+json'; var jsigs = window.jsigs; - var pkey = playground.editors['privatekey-secp256k1'].getValue(); + var privateKey = playground.editors['privatekey-koblitz'].getValue().trim(); // add security context to input if(!('@context' in input)) { @@ -854,11 +855,49 @@ [input['@context'], 'https://w3id.org/security/v1']; } - promise = jsigs.promises.sign(input, { - privateKeyWif: pkey, - algorithm: 'BitcoinSignature2016', - domain: 'example.com', - creator: 'public-key:' + new bitcoreMessage.Bitcore.PrivateKey(pkey).toPublicKey() + var signed = null; + var publicKeyFromPrivateKey; + try { + publicKeyFromPrivateKey = new bitcoreMessage.Bitcore.PrivateKey(privateKey).toPublicKey(); + } catch (e) { + promise = Promise.reject(e) + } + + promise = promise || jsigs.promises.sign(input, { + privateKeyWif: privateKey, + algorithm: 'EcdsaKoblitzSignature2016', + creator: 'ecdsa-koblitz-pubkey:' + publicKeyFromPrivateKey + }).then( function(_signed) { + signed = _signed; + var publicKey = playground.editors['publickey-koblitz'].getValue().trim(); + var verificationKey = { + '@context': jsigs.SECURITY_CONTEXT_URL, + id: 'ecdsa-koblitz-pubkey:' + publicKey, + type: 'CryptographicKey', + publicKeyWif: publicKey + }; + var publicKeyBtcOwner = { + '@context': jsigs.SECURITY_CONTEXT_URL, + publicKey: [verificationKey] + }; + return jsigs.promises.verify(signed, { + publicKey: verificationKey, + publicKeyOwner: publicKeyBtcOwner + }) + }).then( function(verification) { + if (verification) { + $('#validation-message') + .text('Signature validated successfully using this public key') + .show(); + } else { + $('#validation-errors') + .text('Signature was NOT validated using this public key') + .show(); + } + return signed; + }).catch( function(e) { + console.error(e.stack) + throw(e) }); } else { @@ -882,6 +921,8 @@ playground.process = playground._process = function(){ $('#markup-errors').hide().empty(); $('#param-errors').hide().empty(); + $('#validation-errors').hide().empty(); + $('#validation-message').hide().empty(); $('#processing-errors').hide().empty(); $('#using-context-map').hide(); $('#using-context-map table tbody').empty(); @@ -1202,7 +1243,8 @@ hasData = true; editor.setValue(playground.humanize(data[key])); }else{ - if(key !== 'privatekey-rsa' && key !== 'privatekey-secp256k1') { + if(key !== 'privatekey-rsa' && key !== 'privatekey-koblitz' && + key !== 'publickey-koblitz') { editor.setValue("{}"); } } From b0e502ab5c98b6db402bc73cf3610ad673182b54 Mon Sep 17 00:00:00 2001 From: Harlan T Wood and Noah Thorp Date: Thu, 26 Jan 2017 06:08:53 -0800 Subject: [PATCH 2/3] display public key as BTC livenet key --- playground/playground.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playground/playground.js b/playground/playground.js index afc522ba7..dda4a5bf7 100755 --- a/playground/playground.js +++ b/playground/playground.js @@ -858,7 +858,7 @@ var signed = null; var publicKeyFromPrivateKey; try { - publicKeyFromPrivateKey = new bitcoreMessage.Bitcore.PrivateKey(privateKey).toPublicKey(); + publicKeyFromPrivateKey = new bitcoreMessage.Bitcore.PrivateKey(privateKey).toPublicKey().toAddress('livenet'); } catch (e) { promise = Promise.reject(e) } From b8d6b9527c69ca93477004268620ea19a473e0b0 Mon Sep 17 00:00:00 2001 From: Harlan T Wood and Noah Thorp Date: Thu, 26 Jan 2017 06:15:18 -0800 Subject: [PATCH 3/3] Clarify roles of public & private keys --- playground/index.html | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/playground/index.html b/playground/index.html index b67c95ac5..000d0e022 100755 --- a/playground/index.html +++ b/playground/index.html @@ -252,13 +252,13 @@

    -

    Bitcoin Private Key (ECDSA Koblitz)

    +

    Bitcoin (ECDSA Koblitz) Private Key for Signing

    + placeholder="Enter your Bitcoin (Koblitz) private key here..." rows="3">L4mEi7eEdTNNFQEWaa7JhUKAbtHdVvByGAqvpJKC53mfiqunjBjw -

    Bitcoin Public Key (ECDSA Koblitz)

    +

    Bitcoin (ECDSA Koblitz) Public Key for Verification

    + placeholder="Enter your Bitcoin (Koblitz) public key here..." rows="3">1LGpGhGK8whX23ZNdxrgtjKrek9rP4xWER