Skip to content

Commit

Permalink
Add JSON-LD verification via BTC public key (#442)
Browse files Browse the repository at this point in the history
Add JSON-LD verification via BTC public key.

- display public key as BTC livenet key
- Clarify roles of public & private keys
  • Loading branch information
harlantwood authored and dlongley committed Jan 26, 2017
1 parent 1ebf5f3 commit 441ff75
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 40 deletions.
25 changes: 17 additions & 8 deletions playground/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -251,17 +251,26 @@ <h3>
</textarea>
</div>

<div id="privatekey-secp256k1-div" class="span6">
<h3>Bitcoin Private Key (secp256k1)</h3>
<textarea id="privatekey-secp256k1" class="compressed process span6 codemirror-input"
placeholder="Enter your Bitcoin (secp256k1) private key here..." rows="3">L4mEi7eEdTNNFQEWaa7JhUKAbtHdVvByGAqvpJKC53mfiqunjBjw</textarea>
<div id="privatekey-koblitz-div" class="span6">
<h3>Bitcoin (ECDSA Koblitz) Private Key for Signing</h3>
<textarea id="privatekey-koblitz" class="compressed process span6 codemirror-input"
placeholder="Enter your Bitcoin (Koblitz) private key here..." rows="3">L4mEi7eEdTNNFQEWaa7JhUKAbtHdVvByGAqvpJKC53mfiqunjBjw</textarea>

<h3>Bitcoin (ECDSA Koblitz) Public Key for Verification</h3>
<textarea id="publickey-koblitz" class="compressed process span6 codemirror-input"
placeholder="Enter your Bitcoin (Koblitz) public key here..." rows="3">1LGpGhGK8whX23ZNdxrgtjKrek9rP4xWER</textarea>

<div class="koblitz-verification"></div>
</div>


</div>
</div>

<div id="markup-errors" class="hide alert alert-error"></div>
<div id="param-errors" class="hide alert alert-error"></div>
<div id="validation-errors" class="hide alert alert-error"></div>
<div id="validation-message" class="hide alert alert-success"></div>
<div id="using-context-map" class="hide alert alert-note">
<p>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
Expand Down Expand Up @@ -328,7 +337,7 @@ <h3>Bitcoin Private Key (secp256k1)</h3>
</a>
</li>
<li>
<a id="tab-signed-secp256k1" href="#pane-signed-secp256k1" data-toggle="tab" name="tab-signed-secp256k1">
<a id="tab-signed-koblitz" href="#pane-signed-koblitz" data-toggle="tab" name="tab-signed-koblitz">
<i class="icon-pencil"></i>
<span>Signed with Bitcoin</span>
</a>
Expand Down Expand Up @@ -357,8 +366,8 @@ <h3>Bitcoin Private Key (secp256k1)</h3>
<div id="pane-signed-rsa" class="tab-pane">
<textarea id="signed-rsa" class="codemirror-output"></textarea>
</div>
<div id="pane-signed-secp256k1" class="tab-pane">
<textarea id="signed-secp256k1" class="codemirror-output"></textarea>
<div id="pane-signed-koblitz" class="tab-pane">
<textarea id="signed-koblitz" class="codemirror-output"></textarea>
</div>
</div><!-- /.tab-content -->
</div>
Expand Down Expand Up @@ -406,7 +415,7 @@ <h3>Bitcoin Private Key (secp256k1)</h3>
$('#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');
Expand Down
44 changes: 26 additions & 18 deletions playground/jsonld-signatures.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ var libs = {};

api.SECURITY_CONTEXT_URL = 'https://w3id.org/security/v1';
api.SUPPORTED_ALGORITHMS = [
'BitcoinSignature2016',
'EcdsaKoblitzSignature2016',
'GraphSignature2012',
'LinkedDataSignature2015'
];
Expand Down Expand Up @@ -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.'));
Expand Down Expand Up @@ -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);
});
Expand Down Expand Up @@ -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) {
Expand All @@ -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');
Expand All @@ -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));
Expand All @@ -736,20 +738,26 @@ 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) {
// optimize using node libraries
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);
}

Expand All @@ -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);
};
Expand Down
4 changes: 4 additions & 0 deletions playground/playground.css
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,7 @@
#markup-container, #output-container {
margin-top: 1em;
}

#privatekey-koblitz-div .CodeMirror {
height: 100px;
}
70 changes: 56 additions & 14 deletions playground/playground.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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');
Expand All @@ -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('');
}
Expand Down Expand Up @@ -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)) {
Expand All @@ -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().toAddress('livenet');
} 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 {
Expand All @@ -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();
Expand Down Expand Up @@ -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("{}");
}
}
Expand Down

0 comments on commit 441ff75

Please sign in to comment.