var assert = require('assert'); var sjcl = require('ripple-lib').sjcl; describe('ECDSA signing with recoverable public key', function(){ describe('Sign and recover public key from signature', function(){ it('should recover public keys from signatures it generates', function(){ var messages = [{ message: 'Hello world!', secret_hex: '9931c08f61f127d5735fa3c60e702212ce7ed9a2ac90d5dbade99c689728cd9b', random_value: '5473a3dbdc13ec9efbad7f7f929fbbea404af556a48041dd9d41d29fdbc989ad', hash_function: sjcl.hash.sha512.hash // signature: 'AAAAGzFa1pYjhssCpDFZgFSnYQ8qCnMkLaZrg0mXZyNQ2NxgMQ8z9U3ngYerxSZCEt3Q4raMIpt03db7jDNGbfmHy8I=' }, { // Correct recovery value for this one is 0 message: 'ua5pdcG0I1JuhSr9Fwai2UoZ9ll5leUtHE5NzSSNnPkw8nSPH5mT1gE1fe0sn', secret_hex: '84814318ffe6e612694ad59b9084b7b66d68b6979567c619171a67b05e2b654b', random_value: '14261d30b319709c10ab13cabe595313b99dd2d5c76b8b38d7eb445f0b81cc9a', hash_function: sjcl.hash.sha512.hash // signature: 'AAAAHGjpBM7wnTHbPGo0TXsxKbr+d7KvACuJ/eGQsp3ZJfOOQHszaciRo3ClenwKixcquFcBlaVfHlOc3JWOZq1RjpQ=' }, { // Correct recovery value for this one is 1 message: 'rxc76UnmVTp', secret_hex: '37eac47c212be8ea8372f506b11673c281cd9ea29a035c2c9e90d027c3dbecc6', random_value: '61b53ca6de0543f911765ae216a3a4d851918a0733fba9ac80cf29de5bec8032', hash_function: sjcl.hash.sha256.hash // signature: 'AAAAG8L/yOA3nNqK4aOiQWJmOaWvkvr3NoTk6wCdX97U3qowdgFd98UK3evWV16qO3RHgFMEnUW/Vt4+kcidqW6hMo0=' }]; var curve = sjcl.ecc.curves['k256']; for (var m = 0; m < messages.length; m++) { var message = messages[m].message; var secret_hex = messages[m].secret_hex; var random_value = messages[m].random_value; var hash_function = messages[m].hash_function; var secret_bn = sjcl.bn.fromBits(sjcl.codec.hex.toBits(secret_hex)); var secret_key = new sjcl.ecc.ecdsa.secretKey(curve, secret_bn); var pub_val_point = secret_key._curve.G.mult(secret_key._exponent); var public_key = new sjcl.ecc.ecdsa.publicKey(curve, pub_val_point); var hash = hash_function(message); var recoverable_signature = secret_key.signWithRecoverablePublicKey(hash, 0, random_value); var recovered_public_key = sjcl.ecc.ecdsa.publicKey.recoverFromSignature(hash, recoverable_signature); assert.deepEqual(public_key.get().x, recovered_public_key.get().x, 'The x value for the recovered public key did not match for message: ' + message + '. Expected: ' + public_key.get().x.toString() + '. Actual: ' + recovered_public_key.get().x.toString()); assert.deepEqual(public_key.get().y, recovered_public_key.get().y, 'The y value for the recovered public key did not match for message: ' + message + '. Expected: ' + public_key.get().y.toString() + '. Actual: ' + recovered_public_key.get().y.toString()); } }); }); describe('signWithRecoverablePublicKey', function(){ // it('should produce the same values as bitcoinjs-lib\'s implementation', function(){ // // TODO: figure out why bitcoinjs-lib and this produce different signature values // var curve = sjcl.ecc.curves['k256']; // var secret_hex = '9e623166ac44d4e75fa842f3443485b9c8380551132a8ffaa898b5c93bb18b7d'; // var secret_bn = sjcl.bn.fromBits(sjcl.codec.hex.toBits(secret_hex)); // var secret_key = new sjcl.ecc.ecdsa.secretKey(curve, secret_bn); // // var public_key = '0217b9f5b3ba8d550f19fdfb5233818cd27d19aaea029b667f547f5918c307ed3b'; // var random_value = 'c3aa71cecb965bbbc96083d868b4955d77adb4e02ce229fe60869f745dfcd4e4a4d0f17a15a353d7592dca1baba2824e45c8e7a8f9faad3ce2c2d3792799f27a'; // var hash = sjcl.codec.hex.toBits('e865bcc63a86ef21585ac8340a7cc8590ed85175a2a718c6fb2bfb2715d13778'); // var bitcoin_signature_base64 = 'IJPzXewhO1CORRx14FROzZC8ne4v0Me94UZoBKH15e4pcSgeYiYeKZ4PJOBI/D5yqUOhemO+rKKHhE0HL66kAcM='; // var signature = secret_key.signWithRecoverablePublicKey(hash, 0, random_value); // var signature_base64 = sjcl.codec.base64.fromBits(signature); // assert.equal(signature_base64, bitcoin_signature_base64); // }); it('should produce an error if the hash is not given as a bitArray', function(){ var curve = sjcl.ecc.curves['k256']; var secret_hex = '9e623166ac44d4e75fa842f3443485b9c8380551132a8ffaa898b5c93bb18b7d'; var secret_bn = sjcl.bn.fromBits(sjcl.codec.hex.toBits(secret_hex)); var secret_key = new sjcl.ecc.ecdsa.secretKey(curve, secret_bn); var random_value = 'c3aa71cecb965bbbc96083d868b4955d77adb4e02ce229fe60869f745dfcd4e4a4d0f17a15a353d7592dca1baba2824e45c8e7a8f9faad3ce2c2d3792799f27a'; var hash = 'e865bcc63a86ef21585ac8340a7cc8590ed85175a2a718c6fb2bfb2715d13778'; assert.throws(function(){ secret_key.signWithRecoverablePublicKey(hash, 0, random_value); }, /(?=.*hash)(?=.*bitArray).+/); }); it('should return a bitArray', function(){ var curve = sjcl.ecc.curves['k256']; var secret_hex = '9e623166ac44d4e75fa842f3443485b9c8380551132a8ffaa898b5c93bb18b7d'; var secret_bn = sjcl.bn.fromBits(sjcl.codec.hex.toBits(secret_hex)); var secret_key = new sjcl.ecc.ecdsa.secretKey(curve, secret_bn); var random_value = 'c3aa71cecb965bbbc96083d868b4955d77adb4e02ce229fe60869f745dfcd4e4a4d0f17a15a353d7592dca1baba2824e45c8e7a8f9faad3ce2c2d3792799f27a'; var hash = sjcl.codec.hex.toBits('e865bcc63a86ef21585ac8340a7cc8590ed85175a2a718c6fb2bfb2715d13778'); var signature = secret_key.signWithRecoverablePublicKey(hash, 0, random_value); assert(typeof signature === 'object' && signature.length > 0 && typeof signature[0] === 'number'); }); it('should return a bitArray where the first word contains the recovery factor', function(){ var curve = sjcl.ecc.curves['k256']; var secret_hex = '9e623166ac44d4e75fa842f3443485b9c8380551132a8ffaa898b5c93bb18b7d'; var secret_bn = sjcl.bn.fromBits(sjcl.codec.hex.toBits(secret_hex)); var secret_key = new sjcl.ecc.ecdsa.secretKey(curve, secret_bn); var random_value = 'c3aa71cecb965bbbc96083d868b4955d77adb4e02ce229fe60869f745dfcd4e4a4d0f17a15a353d7592dca1baba2824e45c8e7a8f9faad3ce2c2d3792799f27a'; var hash = sjcl.codec.hex.toBits('e865bcc63a86ef21585ac8340a7cc8590ed85175a2a718c6fb2bfb2715d13778'); var signature = secret_key.signWithRecoverablePublicKey(hash, 0, random_value); var recovery_factor = signature[0] - 27; assert(recovery_factor >= 0 && recovery_factor < 4); }); }); describe('recoverFromSignature', function(){ // it('should be able to recover public keys from bitcoinjs-lib\'s implementation', function(){ // // TODO: figure out why bitcoinjs-lib and this produce different signature values // var hash = sjcl.codec.hex.toBits('e865bcc63a86ef21585ac8340a7cc8590ed85175a2a718c6fb2bfb2715d13778'); // var signature = sjcl.codec.base64.toBits('IJPzXewhO1CORRx14FROzZC8ne4v0Me94UZoBKH15e4pcSgeYiYeKZ4PJOBI/D5yqUOhemO+rKKHhE0HL66kAcM='); // var public_key = sjcl.ecc.ecdsa.publicKey.recoverFromSignature(hash, signature); // }); it('should produce an error if the signature given does not have the recovery factor prefix', function(){ var curve = sjcl.ecc.curves['k256']; var secret_hex = '9e623166ac44d4e75fa842f3443485b9c8380551132a8ffaa898b5c93bb18b7d'; var secret_bn = sjcl.bn.fromBits(sjcl.codec.hex.toBits(secret_hex)); var secret_key = new sjcl.ecc.ecdsa.secretKey(curve, secret_bn); var random_value = 'c3aa71cecb965bbbc96083d868b4955d77adb4e02ce229fe60869f745dfcd4e4a4d0f17a15a353d7592dca1baba2824e45c8e7a8f9faad3ce2c2d3792799f27a'; var hash = sjcl.codec.hex.toBits('e865bcc63a86ef21585ac8340a7cc8590ed85175a2a718c6fb2bfb2715d13778'); var signature = secret_key.sign(hash, 0, random_value); assert.throws(function(){ sjcl.ecc.ecdsa.publicKey.recoverFromSignature(hash, signature); }, /(?=.*signature)(?=.*recovery factor)(?=.*public key).*/); }); it('should produce an error if it is not given both the hash and the signature', function(){ var curve = sjcl.ecc.curves['k256']; var secret_hex = '9e623166ac44d4e75fa842f3443485b9c8380551132a8ffaa898b5c93bb18b7d'; var secret_bn = sjcl.bn.fromBits(sjcl.codec.hex.toBits(secret_hex)); var secret_key = new sjcl.ecc.ecdsa.secretKey(curve, secret_bn); var random_value = 'c3aa71cecb965bbbc96083d868b4955d77adb4e02ce229fe60869f745dfcd4e4a4d0f17a15a353d7592dca1baba2824e45c8e7a8f9faad3ce2c2d3792799f27a'; var hash = sjcl.codec.hex.toBits('e865bcc63a86ef21585ac8340a7cc8590ed85175a2a718c6fb2bfb2715d13778'); var signature = secret_key.signWithRecoverablePublicKey(hash, 0, random_value); assert.throws(function(){ sjcl.ecc.ecdsa.publicKey.recoverFromSignature(hash); }, /(?=.*hash\ and\ signature)(?=.*recover\ public\ key).*/); assert.throws(function(){ sjcl.ecc.ecdsa.publicKey.recoverFromSignature(signature); }, /(?=.*hash\ and\ signature)(?=.*recover\ public\ key).*/); }); it('should produce an error if it cannot generate a valid public key from the the signature', function(){ var curve = sjcl.ecc.curves['k256']; var secret_hex = '9e623166ac44d4e75fa842f3443485b9c8380551132a8ffaa898b5c93bb18b7d'; var secret_bn = sjcl.bn.fromBits(sjcl.codec.hex.toBits(secret_hex)); var secret_key = new sjcl.ecc.ecdsa.secretKey(curve, secret_bn); var random_value = 'c3aa71cecb965bbbc96083d868b4955d77adb4e02ce229fe60869f745dfcd4e4a4d0f17a15a353d7592dca1baba2824e45c8e7a8f9faad3ce2c2d3792799f27a'; var hash = sjcl.codec.hex.toBits('e865bcc63a86ef21585ac8340a7cc8590ed85175a2a718c6fb2bfb2715d13778'); var signature = sjcl.codec.base64.toBits('IJPzXewhO1CORRx14FROzZC8ne4v0Me94UZoBKH15e4pcSgeYiYeKZ4PJOBI/D5yqUOhemO+rKKHhE0HL66kAcM='); signature[0] = 27; signature[3] = 0 - signature[3]; assert.throws(function(){ sjcl.ecc.ecdsa.publicKey.recoverFromSignature(hash, signature); }, /(?=.*Cannot\ recover\ public\ key).*/); }); it('should return a publicKey object', function(){ var curve = sjcl.ecc.curves['k256']; var secret_hex = '9e623166ac44d4e75fa842f3443485b9c8380551132a8ffaa898b5c93bb18b7d'; var secret_bn = sjcl.bn.fromBits(sjcl.codec.hex.toBits(secret_hex)); var secret_key = new sjcl.ecc.ecdsa.secretKey(curve, secret_bn); var random_value = 'c3aa71cecb965bbbc96083d868b4955d77adb4e02ce229fe60869f745dfcd4e4a4d0f17a15a353d7592dca1baba2824e45c8e7a8f9faad3ce2c2d3792799f27a'; var hash = sjcl.codec.hex.toBits('e865bcc63a86ef21585ac8340a7cc8590ed85175a2a718c6fb2bfb2715d13778'); var signature = secret_key.signWithRecoverablePublicKey(hash, 0, random_value); var key = sjcl.ecc.ecdsa.publicKey.recoverFromSignature(hash, signature); assert(key instanceof sjcl.ecc.ecdsa.publicKey); }); it('tampering with the signature should produce a different public key, if it produces a valid one at all', function(){ var curve = sjcl.ecc.curves['k256']; var secret_hex = '9e623166ac44d4e75fa842f3443485b9c8380551132a8ffaa898b5c93bb18b7d'; var secret_bn = sjcl.bn.fromBits(sjcl.codec.hex.toBits(secret_hex)); var secret_key = new sjcl.ecc.ecdsa.secretKey(curve, secret_bn); var random_value = 'c3aa71cecb965bbbc96083d868b4955d77adb4e02ce229fe60869f745dfcd4e4a4d0f17a15a353d7592dca1baba2824e45c8e7a8f9faad3ce2c2d3792799f27a'; var hash = sjcl.codec.hex.toBits('e865bcc63a86ef21585ac8340a7cc8590ed85175a2a718c6fb2bfb2715d13778'); var signature = secret_key.signWithRecoverablePublicKey(hash, 0, random_value); signature[3]++; var original_public_key = new sjcl.ecc.ecdsa.publicKey(curve, curve.G.mult(secret_key._exponent)); var recovered_public_key = sjcl.ecc.ecdsa.publicKey.recoverFromSignature(hash, signature); assert.notDeepEqual(original_public_key.get().x, recovered_public_key.get().x); assert.notDeepEqual(original_public_key.get().y, recovered_public_key.get().y); }); it('should return a publicKey object, while using the overridden c256 curve', function(){ var curve = sjcl.ecc.curves['c256']; var secret_hex = '9e623166ac44d4e75fa842f3443485b9c8380551132a8ffaa898b5c93bb18b7d'; var secret_bn = sjcl.bn.fromBits(sjcl.codec.hex.toBits(secret_hex)); var secret_key = new sjcl.ecc.ecdsa.secretKey(curve, secret_bn); var random_value = 'c3aa71cecb965bbbc96083d868b4955d77adb4e02ce229fe60869f745dfcd4e4a4d0f17a15a353d7592dca1baba2824e45c8e7a8f9faad3ce2c2d3792799f27a'; var hash = sjcl.codec.hex.toBits('e865bcc63a86ef21585ac8340a7cc8590ed85175a2a718c6fb2bfb2715d13778'); var signature = secret_key.signWithRecoverablePublicKey(hash, 0, random_value); var key = sjcl.ecc.ecdsa.publicKey.recoverFromSignature(hash, signature); assert(key instanceof sjcl.ecc.ecdsa.publicKey); }); }); });