From ed8f6a2b7f4a189e72568e9c3cf0357d6740dab2 Mon Sep 17 00:00:00 2001 From: Konstantin Shuplenkov Date: Fri, 17 Dec 2021 17:12:22 +0300 Subject: [PATCH 1/6] feat(dpp): implement hashed ECDSA key type --- .../js-dpp/lib/identity/IdentityPublicKey.js | 5 +++ .../AbstractStateTransition.js | 26 +++++++++++- .../AbstractStateTransitionIdentitySigned.js | 23 ++++++++++ ...StateTransitionIdentitySignatureFactory.js | 5 ++- ...tractStateTransitionIdentitySigned.spec.js | 42 ++++++++++++++++++- .../unit/identity/IdentityPublicKey.spec.js | 18 ++++++++ ...TransitionIdentitySignatureFactory.spec.js | 4 +- 7 files changed, 118 insertions(+), 5 deletions(-) diff --git a/packages/js-dpp/lib/identity/IdentityPublicKey.js b/packages/js-dpp/lib/identity/IdentityPublicKey.js index 1ef68b8e236..b3460b979e1 100644 --- a/packages/js-dpp/lib/identity/IdentityPublicKey.js +++ b/packages/js-dpp/lib/identity/IdentityPublicKey.js @@ -143,6 +143,10 @@ class IdentityPublicKey { throw new EmptyPublicKeyDataError(); } + if (this.getType() === IdentityPublicKey.TYPES.ECDSA_HASH160) { + return this.getData(); + } + const originalPublicKey = new PublicKey( this.getData(), ); @@ -199,6 +203,7 @@ class IdentityPublicKey { IdentityPublicKey.TYPES = { ECDSA_SECP256K1: 0, BLS12_381: 1, + ECDSA_HASH160: 2, }; IdentityPublicKey.PURPOSES = { diff --git a/packages/js-dpp/lib/stateTransition/AbstractStateTransition.js b/packages/js-dpp/lib/stateTransition/AbstractStateTransition.js index 369c9436e95..6f07fcf265a 100644 --- a/packages/js-dpp/lib/stateTransition/AbstractStateTransition.js +++ b/packages/js-dpp/lib/stateTransition/AbstractStateTransition.js @@ -1,7 +1,7 @@ const { PublicKey, PrivateKey, - Signer: { sign, verifySignature }, + Signer: { sign, verifySignature, verifyHashSignature }, } = require('@dashevo/dashcore-lib'); const StateTransitionIsNotSignedError = require( @@ -157,6 +157,7 @@ class AbstractStateTransition { * @return {AbstractStateTransition} */ signByPrivateKey(privateKey) { + console.log('pk', privateKey.toString('hex')); const data = this.toBuffer({ skipSignature: true }); const privateKeyModel = new PrivateKey(privateKey); @@ -165,6 +166,29 @@ class AbstractStateTransition { return this; } + /** + * + * @param {Buffer} publicKeyHash + * @return {boolean} + */ + verifySignatureByPublicKeyHash(publicKeyHash) { + const signature = this.getSignature(); + if (!signature) { + throw new StateTransitionIsNotSignedError(this); + } + + const hash = this.hash({ skipSignature: true }); + + let isSignatureVerified; + try { + isSignatureVerified = verifyHashSignature(hash, signature, publicKeyHash); + } catch (e) { + isSignatureVerified = false; + } + + return isSignatureVerified; + } + /** * Verify signature with public key * @param {string|Buffer|Uint8Array|PublicKey} publicKey string must be hex or base58 diff --git a/packages/js-dpp/lib/stateTransition/AbstractStateTransitionIdentitySigned.js b/packages/js-dpp/lib/stateTransition/AbstractStateTransitionIdentitySigned.js index 7dde4bd5ded..5046a3093b9 100644 --- a/packages/js-dpp/lib/stateTransition/AbstractStateTransitionIdentitySigned.js +++ b/packages/js-dpp/lib/stateTransition/AbstractStateTransitionIdentitySigned.js @@ -1,6 +1,7 @@ const { PublicKey, PrivateKey, + crypto: { Hash }, } = require('@dashevo/dashcore-lib'); const AbstractStateTransition = require('./AbstractStateTransition'); @@ -72,6 +73,23 @@ class AbstractStateTransitionIdentitySigned extends AbstractStateTransition { this.signByPrivateKey(privateKeyModel); break; + case IdentityPublicKey.TYPES.ECDSA_HASH160: { + privateKeyModel = new PrivateKey(privateKey); + pubKeyBase = new PublicKey({ + ...privateKeyModel.toPublicKey().toObject(), + compressed: true, + }) + .toBuffer(); + + pubKeyBase = Hash.sha256ripemd160(pubKeyBase); + + if (!pubKeyBase.equals(identityPublicKey.getData())) { + throw new InvalidSignaturePublicKeyError(identityPublicKey.getData()); + } + + this.signByPrivateKey(privateKeyModel); + break; + } case IdentityPublicKey.TYPES.BLS12_381: default: throw new InvalidIdentityPublicKeyTypeError(identityPublicKey.getType()); @@ -123,6 +141,11 @@ class AbstractStateTransitionIdentitySigned extends AbstractStateTransition { } const publicKeyBuffer = publicKey.getData(); + + if (publicKey.getType() === IdentityPublicKey.TYPES.ECDSA_HASH160) { + return this.verifySignatureByPublicKeyHash(publicKeyBuffer); + } + const publicKeyModel = PublicKey.fromBuffer(publicKeyBuffer); return this.verifySignatureByPublicKey(publicKeyModel); diff --git a/packages/js-dpp/lib/stateTransition/validation/validateStateTransitionIdentitySignatureFactory.js b/packages/js-dpp/lib/stateTransition/validation/validateStateTransitionIdentitySignatureFactory.js index a448e01a47c..b36741a2429 100644 --- a/packages/js-dpp/lib/stateTransition/validation/validateStateTransitionIdentitySignatureFactory.js +++ b/packages/js-dpp/lib/stateTransition/validation/validateStateTransitionIdentitySignatureFactory.js @@ -41,7 +41,10 @@ function validateStateTransitionIdentitySignatureFactory( return result; } - if (publicKey.getType() !== IdentityPublicKey.TYPES.ECDSA_SECP256K1) { + if ( + publicKey.getType() !== IdentityPublicKey.TYPES.ECDSA_SECP256K1 + && publicKey.getType() !== IdentityPublicKey.TYPES.ECDSA_HASH160 + ) { result.addError( new InvalidIdentityPublicKeyTypeError(publicKey.getType()), ); diff --git a/packages/js-dpp/test/integration/stateTransition/AbstractStateTransitionIdentitySigned.spec.js b/packages/js-dpp/test/integration/stateTransition/AbstractStateTransitionIdentitySigned.spec.js index 665638fb883..4222f6c1045 100644 --- a/packages/js-dpp/test/integration/stateTransition/AbstractStateTransitionIdentitySigned.spec.js +++ b/packages/js-dpp/test/integration/stateTransition/AbstractStateTransitionIdentitySigned.spec.js @@ -1,4 +1,4 @@ -const { PrivateKey } = require('@dashevo/dashcore-lib'); +const { PrivateKey, crypto: { Hash } } = require('@dashevo/dashcore-lib'); const calculateStateTransitionFee = require('../../../lib/stateTransition/calculateStateTransitionFee'); @@ -127,6 +127,21 @@ describe('AbstractStateTransitionIdentitySigned', () => { expect(isValid).to.be.true(); }); + it('should sign data and validate signature with ECDSA_HASH160 identityPublicKey', () => { + identityPublicKey.setType(IdentityPublicKey.TYPES.ECDSA_HASH160); + identityPublicKey.setData( + Hash.sha256ripemd160(identityPublicKey.getData()), + ); + + stateTransition.sign(identityPublicKey, privateKeyHex); + + expect(stateTransition.signature).to.be.an.instanceOf(Buffer); + + const isValid = stateTransition.verifySignature(identityPublicKey); + + expect(isValid).to.be.true(); + }); + it('should throw an error if we try to sign with wrong public key', () => { const publicKey = new PrivateKey() .toPublicKey() @@ -291,6 +306,31 @@ describe('AbstractStateTransitionIdentitySigned', () => { }); }); + describe('#verifySignatureByPublicKeyHash', () => { + it('should validate sign by public key hash', () => { + privateKeyHex = 'fdfa0d878967ac17ca3e6fa6ca7f647fea51cffac85e41424c6954fcbe97721c'; + const publicKey = 'dLfavDCp+ARA3O0AXsOFJ0W//mg='; + + stateTransition.signByPrivateKey(privateKeyHex); + + const isValid = stateTransition.verifySignatureByPublicKeyHash(Buffer.from(publicKey, 'base64')); + + expect(isValid).to.be.true(); + }); + + it('should throw an StateTransitionIsNotSignedError error if transition is not signed', () => { + const publicKey = 'dLfavDCp+ARA3O0AXsOFJ0W//mg='; + try { + stateTransition.verifySignatureByPublicKeyHash(Buffer.from(publicKey, 'base64')); + + expect.fail('should throw StateTransitionIsNotSignedError'); + } catch (e) { + expect(e).to.be.instanceOf(StateTransitionIsNotSignedError); + expect(e.getStateTransition()).to.equal(stateTransition); + } + }); + }); + describe('#verifySignatureByPublicKey', () => { it('should validate sign by public key', () => { privateKeyHex = '9b67f852093bc61cea0eeca38599dbfba0de28574d2ed9b99d10d33dc1bde7b2'; diff --git a/packages/js-dpp/test/unit/identity/IdentityPublicKey.spec.js b/packages/js-dpp/test/unit/identity/IdentityPublicKey.spec.js index 33dd92ee7d7..eb7749daa24 100644 --- a/packages/js-dpp/test/unit/identity/IdentityPublicKey.spec.js +++ b/packages/js-dpp/test/unit/identity/IdentityPublicKey.spec.js @@ -118,6 +118,24 @@ describe('IdentityPublicKey', () => { expect(result).to.deep.equal(expectedHash); }); + it('should return data in case ECDSA_HASH160', () => { + rawPublicKey = { + id: 0, + type: IdentityPublicKey.TYPES.ECDSA_HASH160, + data: Buffer.from('AkVuTKyF3YgKLAQlLEtaUL2HTditwGILfWUVqjzYnIgH', 'base64'), + purpose: IdentityPublicKey.PURPOSES.AUTHENTICATION, + securityLevel: IdentityPublicKey.SECURITY_LEVELS.MASTER, + }; + + publicKey = new IdentityPublicKey(rawPublicKey); + + const result = publicKey.hash(); + + const expectedHash = Buffer.from('AkVuTKyF3YgKLAQlLEtaUL2HTditwGILfWUVqjzYnIgH', 'base64'); + + expect(result).to.deep.equal(expectedHash); + }); + it('should throw invalid argument error if data was not originally provided', () => { publicKey = new IdentityPublicKey({ id: 0, diff --git a/packages/js-dpp/test/unit/stateTransition/validation/validateStateTransitionIdentitySignatureFactory.spec.js b/packages/js-dpp/test/unit/stateTransition/validation/validateStateTransitionIdentitySignatureFactory.spec.js index 3ed9df449de..6919a2d86c5 100644 --- a/packages/js-dpp/test/unit/stateTransition/validation/validateStateTransitionIdentitySignatureFactory.spec.js +++ b/packages/js-dpp/test/unit/stateTransition/validation/validateStateTransitionIdentitySignatureFactory.spec.js @@ -118,8 +118,8 @@ describe('validateStateTransitionIdentitySignatureFactory', () => { expect(error.getPublicKeyId()).to.equal(publicKeyId); }); - it('should return InvalidIdentityPublicKeyTypeError if type is not ECDSA_SECP256K1', async () => { - const type = IdentityPublicKey.TYPES.ECDSA_SECP256K1 + 1; + it('should return InvalidIdentityPublicKeyTypeError if type is not ECDSA_SECP256K1 and not ECDSA_HASH160', async () => { + const type = IdentityPublicKey.TYPES.ECDSA_HASH160 + 1; identityPublicKey.getType.returns(type); const result = await validateStateTransitionIdentitySignature( From 1364ed4be95d844c39507a060f2646225a563154 Mon Sep 17 00:00:00 2001 From: Konstantin Shuplenkov Date: Fri, 17 Dec 2021 17:28:48 +0300 Subject: [PATCH 2/6] feat(dpp): add readOnly flag to IdentityPublicKey --- .../validateStateTransitionIdentitySignatureFactory.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/js-dpp/test/unit/stateTransition/validation/validateStateTransitionIdentitySignatureFactory.spec.js b/packages/js-dpp/test/unit/stateTransition/validation/validateStateTransitionIdentitySignatureFactory.spec.js index 6919a2d86c5..1d1221e5507 100644 --- a/packages/js-dpp/test/unit/stateTransition/validation/validateStateTransitionIdentitySignatureFactory.spec.js +++ b/packages/js-dpp/test/unit/stateTransition/validation/validateStateTransitionIdentitySignatureFactory.spec.js @@ -130,7 +130,7 @@ describe('validateStateTransitionIdentitySignatureFactory', () => { expect(result.isValid()).to.be.false(); expect(validateIdentityExistenceMock).to.be.calledOnceWithExactly(ownerId); expect(identity.getPublicKeyById).to.be.calledOnceWithExactly(publicKeyId); - expect(identityPublicKey.getType).to.be.calledTwice(); + expect(identityPublicKey.getType).to.be.calledThrice(); expect(stateTransition.getSignaturePublicKeyId).to.be.calledOnce(); expect(stateTransition.verifySignature).to.not.be.called(); From 1ee15129b6af865ad21c22f9e47b40349b6c275c Mon Sep 17 00:00:00 2001 From: Konstantin Shuplenkov Date: Fri, 17 Dec 2021 17:41:59 +0300 Subject: [PATCH 3/6] feat(dpp): data contract --- packages/js-dpp/schema/identity/publicKey.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/js-dpp/schema/identity/publicKey.json b/packages/js-dpp/schema/identity/publicKey.json index 7492cc88fb2..68007e8a64d 100644 --- a/packages/js-dpp/schema/identity/publicKey.json +++ b/packages/js-dpp/schema/identity/publicKey.json @@ -12,9 +12,10 @@ "type": "integer", "enum": [ 0, - 1 + 1, + 2 ], - "description": "Public key type. 0 - ECDSA Secp256k1, 1 - BLS 12-381", + "description": "Public key type. 0 - ECDSA Secp256k1, 1 - BLS 12-381, 2 - ECDSA Secp256k1 Hash160", "$comment": "It can't be changed after adding a key" }, "purpose": { From 0e2bb8826f7ed969e0836f152caf95c261a95b54 Mon Sep 17 00:00:00 2001 From: Konstantin Shuplenkov Date: Tue, 21 Dec 2021 15:59:09 +0300 Subject: [PATCH 4/6] chore: fix PR comments --- packages/js-dpp/lib/stateTransition/AbstractStateTransition.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/js-dpp/lib/stateTransition/AbstractStateTransition.js b/packages/js-dpp/lib/stateTransition/AbstractStateTransition.js index 6f07fcf265a..208c0695edc 100644 --- a/packages/js-dpp/lib/stateTransition/AbstractStateTransition.js +++ b/packages/js-dpp/lib/stateTransition/AbstractStateTransition.js @@ -157,7 +157,6 @@ class AbstractStateTransition { * @return {AbstractStateTransition} */ signByPrivateKey(privateKey) { - console.log('pk', privateKey.toString('hex')); const data = this.toBuffer({ skipSignature: true }); const privateKeyModel = new PrivateKey(privateKey); From 45077b9b134484ecc049c67c0292d3a6ffaf36cb Mon Sep 17 00:00:00 2001 From: Konstantin Shuplenkov Date: Tue, 21 Dec 2021 16:35:53 +0300 Subject: [PATCH 5/6] chore: fix PR comments --- .../js-dpp/schema/identity/publicKey.json | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/packages/js-dpp/schema/identity/publicKey.json b/packages/js-dpp/schema/identity/publicKey.json index 68007e8a64d..1a01cc77b4b 100644 --- a/packages/js-dpp/schema/identity/publicKey.json +++ b/packages/js-dpp/schema/identity/publicKey.json @@ -83,6 +83,27 @@ } } } + }, + { + "if": { + "properties": { + "type": { + "const": 2 + } + } + }, + "then": { + "properties": { + "data": { + "type": "array", + "byteArray": true, + "minItems": 20, + "maxItems": 20, + "description": "BLS public key Hash160", + "$comment": "It must be a valid key hash of the specified type and unique for the identity. It can’t be changed after adding a key" + } + } + } } ], "required": [ From 3e62bc617941b36b0c307aa3e1bd7eec2d4cbf35 Mon Sep 17 00:00:00 2001 From: Konstantin Shuplenkov Date: Tue, 21 Dec 2021 17:18:44 +0300 Subject: [PATCH 6/6] chore: fix PR comments --- packages/js-dpp/schema/identity/publicKey.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/js-dpp/schema/identity/publicKey.json b/packages/js-dpp/schema/identity/publicKey.json index 1a01cc77b4b..891ac676cde 100644 --- a/packages/js-dpp/schema/identity/publicKey.json +++ b/packages/js-dpp/schema/identity/publicKey.json @@ -99,7 +99,7 @@ "byteArray": true, "minItems": 20, "maxItems": 20, - "description": "BLS public key Hash160", + "description": "ECDSA Secp256k1 public key Hash160", "$comment": "It must be a valid key hash of the specified type and unique for the identity. It can’t be changed after adding a key" } }