Skip to content

Commit e4551ad

Browse files
committed
Add JSON-LD verification via BTC public key
1 parent 4a2ebde commit e4551ad

File tree

4 files changed

+112
-43
lines changed

4 files changed

+112
-43
lines changed

playground/index.html

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -251,17 +251,26 @@ <h3>
251251
</textarea>
252252
</div>
253253

254-
<div id="privatekey-secp256k1-div" class="span6">
255-
<h3>Bitcoin Private Key (secp256k1)</h3>
256-
<textarea id="privatekey-secp256k1" class="compressed process span6 codemirror-input"
257-
placeholder="Enter your Bitcoin (secp256k1) private key here..." rows="3">L4mEi7eEdTNNFQEWaa7JhUKAbtHdVvByGAqvpJKC53mfiqunjBjw</textarea>
254+
<div id="privatekey-koblitz-div" class="span6">
255+
<h3>Bitcoin Private Key (ECDSA Koblitz)</h3>
256+
<textarea id="privatekey-koblitz" class="compressed process span6 codemirror-input"
257+
placeholder="Enter your Bitcoin (koblitz) private key here..." rows="3">L4mEi7eEdTNNFQEWaa7JhUKAbtHdVvByGAqvpJKC53mfiqunjBjw</textarea>
258+
259+
<h3>Bitcoin Public Key (ECDSA Koblitz)</h3>
260+
<textarea id="publickey-koblitz" class="compressed process span6 codemirror-input"
261+
placeholder="Enter your Bitcoin (koblitz) public key here..." rows="3">1LGpGhGK8whX23ZNdxrgtjKrek9rP4xWER</textarea>
262+
263+
<div class="koblitz-verification"></div>
258264
</div>
259265

266+
260267
</div>
261268
</div>
262269

263270
<div id="markup-errors" class="hide alert alert-error"></div>
264271
<div id="param-errors" class="hide alert alert-error"></div>
272+
<div id="validation-errors" class="hide alert alert-error"></div>
273+
<div id="validation-message" class="hide alert alert-success"></div>
265274
<div id="using-context-map" class="hide alert alert-note">
266275
<p>NOTE: A remote context that is not known to be fully working yet was detected in your input.
267276
If you wish, you can use an alternative context created by the JSON-LD community to
@@ -328,7 +337,7 @@ <h3>Bitcoin Private Key (secp256k1)</h3>
328337
</a>
329338
</li>
330339
<li>
331-
<a id="tab-signed-secp256k1" href="#pane-signed-secp256k1" data-toggle="tab" name="tab-signed-secp256k1">
340+
<a id="tab-signed-koblitz" href="#pane-signed-koblitz" data-toggle="tab" name="tab-signed-koblitz">
332341
<i class="icon-pencil"></i>
333342
<span>Signed with Bitcoin</span>
334343
</a>
@@ -357,8 +366,8 @@ <h3>Bitcoin Private Key (secp256k1)</h3>
357366
<div id="pane-signed-rsa" class="tab-pane">
358367
<textarea id="signed-rsa" class="codemirror-output"></textarea>
359368
</div>
360-
<div id="pane-signed-secp256k1" class="tab-pane">
361-
<textarea id="signed-secp256k1" class="codemirror-output"></textarea>
369+
<div id="pane-signed-koblitz" class="tab-pane">
370+
<textarea id="signed-koblitz" class="codemirror-output"></textarea>
362371
</div>
363372
</div><!-- /.tab-content -->
364373
</div>
@@ -406,7 +415,7 @@ <h3>Bitcoin Private Key (secp256k1)</h3>
406415
$('#context-div').hide();
407416
$('#frame-div').hide();
408417
$('#privatekey-rsa-div').hide();
409-
$('#privatekey-secp256k1-div').hide();
418+
$('#privatekey-koblitz-div').hide();
410419
$('#markup,#context,#frame').bind('keyup', function() {
411420
$('.btn-group > .btn').each(function () {
412421
$(this).removeClass('active');

playground/jsonld-signatures.js

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ var libs = {};
6868

6969
api.SECURITY_CONTEXT_URL = 'https://w3id.org/security/v1';
7070
api.SUPPORTED_ALGORITHMS = [
71-
'BitcoinSignature2016',
71+
'EcdsaKoblitzSignature2016',
7272
'GraphSignature2012',
7373
'LinkedDataSignature2015'
7474
];
@@ -144,11 +144,12 @@ api.sign = function(input, options, callback) {
144144

145145
if(api.SUPPORTED_ALGORITHMS.indexOf(algorithm) === -1) {
146146
return callback(new Error(
147-
'[jsig.sign] options.algorithm must be one of: ' +
147+
'[jsigs.sign] Unsupported algorithm "' + algorithm + '"; ' +
148+
'options.algorithm must be one of: ' +
148149
JSON.stringify(api.SUPPORTED_ALGORITHMS)));
149150
}
150151

151-
if(algorithm === 'BitcoinSignature2016') {
152+
if(algorithm === 'EcdsaKoblitzSignature2016') {
152153
if(typeof privateKeyWif !== 'string') {
153154
return callback(new TypeError(
154155
'[jsig.sign] options.privateKeyWif must be a base 58 formatted string.'));
@@ -297,10 +298,12 @@ api.verify = function(input, options, callback) {
297298
return callback(new Error('[jsigs.verify] No signature found.'));
298299
}
299300
var algorithm = jsonld.getValues(signature, 'type')[0] || '';
301+
algorithm = algorithm.replace(/^.+:/, ''); // strip off any namespace to compare to known algorithm names
300302
if(api.SUPPORTED_ALGORITHMS.indexOf(algorithm) === -1) {
301303
return callback(new Error(
302-
'[jsigs.verify] Unsupported signature algorithm; supported ' +
303-
'algorithms are: ' + JSON.stringify(api.SUPPORTED_ALGORITHMS)));
304+
'[jsigs.verify] Unsupported signature algorithm "' + algorithm + '"; ' +
305+
'supported algorithms are: ' +
306+
JSON.stringify(api.SUPPORTED_ALGORITHMS)));
304307
}
305308
return _verify(algorithm, input, options, callback);
306309
});
@@ -678,13 +681,14 @@ function _verify(algorithm, input, options, callback) {
678681
* @param callback(err, signature) called once the operation completes.
679682
*/
680683
var _createSignature = function(input, options, callback) {
681-
if(options.algorithm === 'BitcoinSignature2016') {
684+
var signature, privateKey;
685+
686+
if(options.algorithm === 'EcdsaKoblitzSignature2016') {
682687
// works same in any environment
683-
var signature;
684688
try {
685689
var bitcoreMessage = api.use('bitcoreMessage');
686690
var bitcore = bitcoreMessage.Bitcore;
687-
var privateKey = bitcore.PrivateKey.fromWIF(options.privateKeyWif);
691+
privateKey = bitcore.PrivateKey.fromWIF(options.privateKeyWif);
688692
var message = bitcoreMessage(_getDataToHash(input, options));
689693
signature = message.sign(privateKey);
690694
} catch(err) {
@@ -695,7 +699,6 @@ var _createSignature = function(input, options, callback) {
695699

696700
if(_nodejs) {
697701
// optimize using node libraries
698-
var signature;
699702
try {
700703
var crypto = api.use('crypto');
701704
var signer = crypto.createSign('RSA-SHA256');
@@ -708,10 +711,9 @@ var _createSignature = function(input, options, callback) {
708711
}
709712

710713
// browser or other environment
711-
var signature;
712714
try {
713715
var forge = api.use('forge');
714-
var privateKey = forge.pki.privateKeyFromPem(options.privateKeyPem);
716+
privateKey = forge.pki.privateKeyFromPem(options.privateKeyPem);
715717
var md = forge.md.sha256.create();
716718
md.update(_getDataToHash(input, options), 'utf8');
717719
signature = forge.util.encode64(privateKey.sign(md));
@@ -736,20 +738,26 @@ var _createSignature = function(input, options, callback) {
736738
* @param callback(err, valid) called once the operation completes.
737739
*/
738740
var _verifySignature = function(input, signature, options, callback) {
739-
if(options.algorithm === 'BitcoinSignature2016') {
741+
var verified;
742+
743+
if(options.algorithm === 'EcdsaKoblitzSignature2016') {
740744
// works same in any environment
741-
var bitcoreMessage = api.use('bitcoreMessage');
742-
var message = bitcoreMessage(_getDataToHash(input, options));
743-
var verified = message.verify(options.publicKeyWif, signature);
744-
return callback(null, verified);
745+
try {
746+
var bitcoreMessage = api.use('bitcoreMessage');
747+
var message = bitcoreMessage(_getDataToHash(input, options));
748+
verified = message.verify(options.publicKeyWif, signature);
749+
return callback(null, verified);
750+
} catch (err) {
751+
return callback(err);
752+
}
745753
}
746754

747755
if(_nodejs) {
748756
// optimize using node libraries
749757
var crypto = api.use('crypto');
750758
var verifier = crypto.createVerify('RSA-SHA256');
751759
verifier.update(_getDataToHash(input, options), 'utf8');
752-
var verified = verifier.verify(options.publicKeyPem, signature, 'base64');
760+
verified = verifier.verify(options.publicKeyPem, signature, 'base64');
753761
return callback(null, verified);
754762
}
755763

@@ -758,7 +766,7 @@ var _verifySignature = function(input, signature, options, callback) {
758766
var publicKey = forge.pki.publicKeyFromPem(options.publicKeyPem);
759767
var md = forge.md.sha256.create();
760768
md.update(_getDataToHash(input, options), 'utf8');
761-
var verified = publicKey.verify(
769+
verified = publicKey.verify(
762770
md.digest().bytes(), forge.util.decode64(signature));
763771
callback(null, verified);
764772
};

playground/playground.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,3 +73,7 @@
7373
#markup-container, #output-container {
7474
margin-top: 1em;
7575
}
76+
77+
#privatekey-koblitz-div .CodeMirror {
78+
height: 100px;
79+
}

playground/playground.js

Lines changed: 65 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -383,7 +383,8 @@
383383
var editor;
384384

385385
// don't use JSON-LD for PEM data
386-
if(key === 'privatekey-rsa' || key === 'privatekey-secp256k1') {
386+
if(key === 'privatekey-rsa' || key === 'privatekey-koblitz' ||
387+
key === 'publickey-koblitz') {
387388
editor = CodeMirror.fromTextArea(node, {
388389
matchBrackets: true,
389390
autoCloseBrackets: true,
@@ -739,10 +740,10 @@
739740
var id = playground.activeTab = evt.target.id;
740741

741742
if(['tab-compacted', 'tab-flattened', 'tab-framed',
742-
'tab-signed-rsa', 'tab-signed-secp256k1', ].indexOf(id) > -1) {
743+
'tab-signed-rsa', 'tab-signed-koblitz', ].indexOf(id) > -1) {
743744
// these options require more UI inputs, so compress UI space
744745
$('#markup-div').removeClass('span12').addClass('span6');
745-
$('#frame-div, #privatekey-rsa-div, #privatekey-secp256k1-div, ' +
746+
$('#frame-div, #privatekey-rsa-div, #privatekey-koblitz-div, ' +
746747
'#context-div').hide();
747748
if(id === 'tab-compacted' || id === 'tab-flattened') {
748749
$('#param-type').html('JSON-LD Context');
@@ -753,15 +754,15 @@
753754
} else if(id === 'tab-signed-rsa') {
754755
$('#param-type').html('PEM-encoded Private Key');
755756
$('#privatekey-rsa-div').show();
756-
} else if(id === 'tab-signed-secp256k1') {
757+
} else if(id === 'tab-signed-koblitz') {
757758
$('#param-type').html('Base 58 Encoded Private Key');
758-
$('#privatekey-secp256k1-div').show();
759+
$('#privatekey-koblitz-div').show();
759760
}
760761
}
761762
else {
762763
// else no input textarea required
763764
$('#context-div, #frame-div, #privatekey-rsa-div, ' +
764-
'#privatekey-secp256k1-div').hide();
765+
'#privatekey-koblitz-div').hide();
765766
$('#markup-div').removeClass('span6').addClass('span12');
766767
$('#param-type').html('');
767768
}
@@ -838,27 +839,71 @@
838839
creator: 'https://example.com/jdoe/keys/1'
839840
});
840841
}
841-
else if(playground.activeTab === 'tab-signed-secp256k1') {
842+
else if(playground.activeTab === 'tab-signed-koblitz') {
842843
options.format = 'application/ld+json';
843844

844845
var jsigs = window.jsigs;
845-
var pkey = playground.editors['privatekey-secp256k1'].getValue();
846+
var privateKey = playground.editors['privatekey-koblitz'].getValue().trim();
847+
848+
// temporary, until EcdsaKoblitzSignature2016 is added to
849+
// default security context.
850+
// see also: https://github.com/web-payments/web-payments.org/issues/41
851+
var NEAR_FUTURE_SECURITY_CONTEXT =
852+
"https://comakery.github.io/json-ld.org/schemas/security-v1-patch.jsonld";
846853

847854
// add security context to input
848855
if(!('@context' in input)) {
849-
input['@context'] = 'https://w3id.org/security/v1';
856+
input['@context'] = NEAR_FUTURE_SECURITY_CONTEXT;
850857
} else if(Array.isArray(input['@context'])) {
851-
input['@context'].push('https://w3id.org/security/v1');
858+
input['@context'].push(NEAR_FUTURE_SECURITY_CONTEXT);
852859
} else {
853860
input['@context'] =
854-
[input['@context'], 'https://w3id.org/security/v1'];
861+
[input['@context'], NEAR_FUTURE_SECURITY_CONTEXT];
855862
}
856863

857-
promise = jsigs.promises.sign(input, {
858-
privateKeyWif: pkey,
859-
algorithm: 'BitcoinSignature2016',
860-
domain: 'example.com',
861-
creator: 'public-key:' + new bitcoreMessage.Bitcore.PrivateKey(pkey).toPublicKey()
864+
var signed = null;
865+
var publicKeyFromPrivateKey;
866+
try {
867+
publicKeyFromPrivateKey = new bitcoreMessage.Bitcore.PrivateKey(privateKey).toPublicKey();
868+
} catch (e) {
869+
promise = Promise.reject(e)
870+
}
871+
872+
promise = promise || jsigs.promises.sign(input, {
873+
privateKeyWif: privateKey,
874+
algorithm: 'EcdsaKoblitzSignature2016',
875+
creator: 'ecdsa-koblitz-pubkey:' + publicKeyFromPrivateKey
876+
}).then( function(_signed) {
877+
signed = _signed;
878+
var publicKey = playground.editors['publickey-koblitz'].getValue().trim();
879+
var verificationKey = {
880+
'@context': jsigs.SECURITY_CONTEXT_URL,
881+
id: 'ecdsa-koblitz-pubkey:' + publicKey,
882+
type: 'CryptographicKey',
883+
publicKeyWif: publicKey
884+
};
885+
var publicKeyBtcOwner = {
886+
'@context': jsigs.SECURITY_CONTEXT_URL,
887+
publicKey: [verificationKey]
888+
};
889+
return jsigs.promises.verify(signed, {
890+
publicKey: verificationKey,
891+
publicKeyOwner: publicKeyBtcOwner
892+
})
893+
}).then( function(verification) {
894+
if (verification) {
895+
$('#validation-message')
896+
.text('Signature validated successfully using this public key')
897+
.show();
898+
} else {
899+
$('#validation-errors')
900+
.text('Signature was NOT validated using this public key')
901+
.show();
902+
}
903+
return signed;
904+
}).catch( function(e) {
905+
console.error(e.stack)
906+
throw(e)
862907
});
863908
}
864909
else {
@@ -882,6 +927,8 @@
882927
playground.process = playground._process = function(){
883928
$('#markup-errors').hide().empty();
884929
$('#param-errors').hide().empty();
930+
$('#validation-errors').hide().empty();
931+
$('#validation-message').hide().empty();
885932
$('#processing-errors').hide().empty();
886933
$('#using-context-map').hide();
887934
$('#using-context-map table tbody').empty();
@@ -1202,7 +1249,8 @@
12021249
hasData = true;
12031250
editor.setValue(playground.humanize(data[key]));
12041251
}else{
1205-
if(key !== 'privatekey-rsa' && key !== 'privatekey-secp256k1') {
1252+
if(key !== 'privatekey-rsa' && key !== 'privatekey-koblitz' &&
1253+
key !== 'publickey-koblitz') {
12061254
editor.setValue("{}");
12071255
}
12081256
}

0 commit comments

Comments
 (0)