Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions packages/js-dpp/lib/identity/IdentityPublicKey.js
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
);
Expand Down Expand Up @@ -199,6 +203,7 @@ class IdentityPublicKey {
IdentityPublicKey.TYPES = {
ECDSA_SECP256K1: 0,
BLS12_381: 1,
ECDSA_HASH160: 2,
};

IdentityPublicKey.PURPOSES = {
Expand Down
25 changes: 24 additions & 1 deletion packages/js-dpp/lib/stateTransition/AbstractStateTransition.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const {
PublicKey,
PrivateKey,
Signer: { sign, verifySignature },
Signer: { sign, verifySignature, verifyHashSignature },
} = require('@dashevo/dashcore-lib');

const StateTransitionIsNotSignedError = require(
Expand Down Expand Up @@ -165,6 +165,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
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const {
PublicKey,
PrivateKey,
crypto: { Hash },
} = require('@dashevo/dashcore-lib');

const AbstractStateTransition = require('./AbstractStateTransition');
Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()),
);
Expand Down
26 changes: 24 additions & 2 deletions packages/js-dpp/schema/identity/publicKey.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Comment thread
shumkov marked this conversation as resolved.
Expand Down Expand Up @@ -82,6 +83,27 @@
}
}
}
},
{
"if": {
"properties": {
"type": {
"const": 2
}
}
},
"then": {
"properties": {
"data": {
"type": "array",
"byteArray": true,
"minItems": 20,
"maxItems": 20,
"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"
}
}
}
}
],
"required": [
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const { PrivateKey } = require('@dashevo/dashcore-lib');
const { PrivateKey, crypto: { Hash } } = require('@dashevo/dashcore-lib');

const calculateStateTransitionFee = require('../../../lib/stateTransition/calculateStateTransitionFee');

Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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';
Expand Down
18 changes: 18 additions & 0 deletions packages/js-dpp/test/unit/identity/IdentityPublicKey.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,24 @@ describe('IdentityPublicKey', () => {
expect(result).to.deep.equal(expectedHash);
});

it('should return data in case ECDSA_HASH160', () => {
Comment thread
shumkov marked this conversation as resolved.
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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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();

Expand Down