-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #169 from identity-com/CIV-3254_merkletree_signature
CIV-3154 Merkletree Sign and Verify
- Loading branch information
Showing
5 changed files
with
266 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
/* eslint-disable max-len */ | ||
|
||
const { HDNode } = require('bitcoinjs-lib'); | ||
const CredentialSignerVerifier = require('../src/creds/CredentialSignerVerifier'); | ||
|
||
const SEED = 'f6d466fd58c20ff964673522083efebf'; | ||
const prvBase58 = 'xprv9s21ZrQH143K4aBUwUW6GVec7Y6oUEBqrt2WWaXyxjh2pjofNc1of44BLufn4p1t7Jq4EPzm5C9sRxCuBYJdHu62jhgfyPm544sNjtH7x8S'; | ||
|
||
const pubBase58 = 'xpub661MyMwAqRbcH4Fx3W36ddbLfZwHsguhE6x7JxwbX5E1hY8ov9L4CrNfCCQpV8pVK64CVqkhYQ9QLFgkVAUqkRThkTY1R4GiWHNZtAFSVpD'; | ||
|
||
describe('CredentialSignerVerifier Tests', () => { | ||
describe('Using a ECKeyPair', () => { | ||
let keyPair; | ||
let signerVerifier; | ||
|
||
beforeAll(() => { | ||
keyPair = HDNode.fromSeedHex(SEED); | ||
signerVerifier = new CredentialSignerVerifier({ keyPair }); | ||
}); | ||
|
||
it('Should sign and verify', () => { | ||
const toSign = { merkleRoot: 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855' }; | ||
const signature = signerVerifier.sign(toSign); | ||
expect(signature).toBeDefined(); | ||
const toVerify = { | ||
proof: { | ||
...toSign, | ||
merkleRootSignature: signature, | ||
}, | ||
}; | ||
expect(signerVerifier.isSignatureValid(toVerify)).toBeTruthy(); | ||
}); | ||
|
||
it('Should verify', () => { | ||
const toVerify = { | ||
proof: { | ||
merkleRoot: 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b666', | ||
merkleRootSignature: { | ||
algo: 'ec256k1', | ||
pubBase58: 'xpub661MyMwAqRbcH4Fx3W36ddbLfZwHsguhE6x7JxwbX5E1hY8ov9L4CrNfCCQpV8pVK64CVqkhYQ9QLFgkVAUqkRThkTY1R4GiWHNZtAFSVpD', | ||
signature: '3045022100e7f0921491e8da2759b24047443325483ac023795683dc3b91c78d0566a1159602206fd4e80982fd83705932543d02bc6abd079446bf4ec7b5d9fba4f7f5363bd6fa', | ||
}, | ||
}, | ||
}; | ||
|
||
expect(signerVerifier.isSignatureValid(toVerify)).toBeTruthy(); | ||
}); | ||
|
||
it('Should not verify', () => { | ||
const toVerify = { | ||
proof: { | ||
merkleRoot: 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b666', | ||
merkleRootSignature: { | ||
algo: 'ec256k1', | ||
pubBase58: 'xpub661MyMwAqRbcH4Fx3W36ddbLfZwHsguhE6x7JxwbX5E1hY8ov9L4CrNfCCQpV8pVK64CVqkhYQ9QLFgkVAUqkRThkTY1R4GiWHNZtAFSVpD', | ||
signature: 'fa3e022100e7f0921491e8da2759b24047443325483ac023795683dc3b91c78d0566a1159602206fd4e80982fd83705932543d02bc6abd079446bf4ec7b5d9fba4f7f5363bd6fa', | ||
}, | ||
}, | ||
}; | ||
expect(signerVerifier.isSignatureValid(toVerify)).toBeFalsy(); | ||
}); | ||
}); | ||
describe('Using a prvBase58', () => { | ||
let signerVerifier; | ||
|
||
beforeAll(() => { | ||
signerVerifier = new CredentialSignerVerifier({ prvBase58 }); | ||
}); | ||
|
||
it('Should sign and verify', () => { | ||
const toSign = { merkleRoot: 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855' }; | ||
const signature = signerVerifier.sign(toSign); | ||
expect(signature).toBeDefined(); | ||
const toVerify = { | ||
proof: { | ||
...toSign, | ||
merkleRootSignature: signature, | ||
}, | ||
}; | ||
expect(signerVerifier.isSignatureValid(toVerify)).toBeTruthy(); | ||
}); | ||
|
||
it('Should verify', () => { | ||
const toVerify = { | ||
proof: { | ||
merkleRoot: 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b666', | ||
merkleRootSignature: { | ||
algo: 'ec256k1', | ||
pubBase58: 'xpub661MyMwAqRbcH4Fx3W36ddbLfZwHsguhE6x7JxwbX5E1hY8ov9L4CrNfCCQpV8pVK64CVqkhYQ9QLFgkVAUqkRThkTY1R4GiWHNZtAFSVpD', | ||
signature: '3045022100e7f0921491e8da2759b24047443325483ac023795683dc3b91c78d0566a1159602206fd4e80982fd83705932543d02bc6abd079446bf4ec7b5d9fba4f7f5363bd6fa', | ||
}, | ||
}, | ||
}; | ||
expect(signerVerifier.isSignatureValid(toVerify)).toBeTruthy(); | ||
}); | ||
|
||
it('Should not verify', () => { | ||
const toVerify = { | ||
proof: { | ||
merkleRoot: 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b666', | ||
merkleRootSignature: { | ||
algo: 'ec256k1', | ||
pubBase58: 'xpub661MyMwAqRbcH4Fx3W36ddbLfZwHsguhE6x7JxwbX5E1hY8ov9L4CrNfCCQpV8pVK64CVqkhYQ9QLFgkVAUqkRThkTY1R4GiWHNZtAFSVpD', | ||
signature: 'fa3e022100e7f0921491e8da2759b24047443325483ac023795683dc3b91c78d0566a1159602206fd4e80982fd83705932543d02bc6abd079446bf4ec7b5d9fba4f7f5363bd6fa', | ||
}, | ||
}, | ||
}; | ||
expect(signerVerifier.isSignatureValid(toVerify)).toBeFalsy(); | ||
}); | ||
}); | ||
describe('Using a pubBase58', () => { | ||
let signerVerifier; | ||
|
||
beforeAll(() => { | ||
signerVerifier = new CredentialSignerVerifier({ pubBase58 }); | ||
}); | ||
|
||
it('Should verify', () => { | ||
const toVerify = { | ||
proof: { | ||
merkleRoot: 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b666', | ||
merkleRootSignature: { | ||
algo: 'ec256k1', | ||
pubBase58: 'xpub661MyMwAqRbcH4Fx3W36ddbLfZwHsguhE6x7JxwbX5E1hY8ov9L4CrNfCCQpV8pVK64CVqkhYQ9QLFgkVAUqkRThkTY1R4GiWHNZtAFSVpD', | ||
signature: '3045022100e7f0921491e8da2759b24047443325483ac023795683dc3b91c78d0566a1159602206fd4e80982fd83705932543d02bc6abd079446bf4ec7b5d9fba4f7f5363bd6fa', | ||
}, | ||
}, | ||
}; | ||
expect(signerVerifier.isSignatureValid(toVerify)).toBeTruthy(); | ||
}); | ||
|
||
it('Should not verify', () => { | ||
const toVerify = { | ||
proof: { | ||
merkleRoot: 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b666', | ||
merkleRootSignature: { | ||
algo: 'ec256k1', | ||
pubBase58: 'xpub661MyMwAqRbcH4Fx3W36ddbLfZwHsguhE6x7JxwbX5E1hY8ov9L4CrNfCCQpV8pVK64CVqkhYQ9QLFgkVAUqkRThkTY1R4GiWHNZtAFSVpD', | ||
signature: 'fa3e022100e7f0921491e8da2759b24047443325483ac023795683dc3b91c78d0566a1159602206fd4e80982fd83705932543d02bc6abd079446bf4ec7b5d9fba4f7f5363bd6fa', | ||
}, | ||
}, | ||
}; | ||
expect(signerVerifier.isSignatureValid(toVerify)).toBeFalsy(); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
const _ = require('lodash'); | ||
const { HDNode, ECSignature } = require('bitcoinjs-lib'); | ||
|
||
const SIGNATURE_ALGO = 'ec256k1'; | ||
class CredentialSignerVerifier { | ||
/** | ||
* Creates a new instance of a CredentialSignerVerifier | ||
* | ||
* @param options.keyPair any instace that implements sign and verify interface | ||
* or | ||
* @param options.prvBase58 bse58 serialized private key | ||
* or for verification only | ||
* @param options.pubBase58 bse58 serialized public key | ||
*/ | ||
constructor(options) { | ||
if (_.isEmpty(options.keyPair) && _.isEmpty(options.prvBase58) && _.isEmpty(options.pubBase58)) { | ||
throw new Error('Either a keyPair, prvBase58 or pubBase58(to verify only) is required'); | ||
} | ||
this.keyPair = options.keyPair || HDNode.fromBase58(options.prvBase58 || options.pubBase58); | ||
} | ||
|
||
/** | ||
* Verify is a credential has a valid merkletree signature, using a pinned pubkey | ||
* @param credential | ||
* @returns {*|boolean} | ||
*/ | ||
isSignatureValid(credential) { | ||
if (_.isEmpty(credential.proof) | ||
|| _.isEmpty(credential.proof.merkleRoot) | ||
|| _.isEmpty(credential.proof.merkleRootSignature)) { | ||
throw Error('Invalid Credential Proof Schema'); | ||
} | ||
|
||
try { | ||
const signatureHex = _.get(credential, 'proof.merkleRootSignature.signature'); | ||
const signature = signatureHex ? ECSignature.fromDER(Buffer.from(signatureHex, 'hex')) : null; | ||
const merkleRoot = _.get(credential, 'proof.merkleRoot'); | ||
return (signature && merkleRoot) ? this.keyPair.verify(Buffer.from(merkleRoot, 'hex'), signature) : false; | ||
} catch (error) { | ||
// verify throws in must cases but we want to return false | ||
return false; | ||
} | ||
} | ||
|
||
/** | ||
* Create a merkleRootSignature object by signing with a pinned private key | ||
* @param proof | ||
* @returns {{signature, pubBase58: *, algo: string}} | ||
*/ | ||
sign(proof) { | ||
const hash = Buffer.from(proof.merkleRoot, 'hex'); | ||
const signature = this.keyPair.sign(hash); | ||
return { | ||
algo: SIGNATURE_ALGO, | ||
pubBase58: this.keyPair.neutered().toBase58(), | ||
signature: signature.toDER().toString('hex'), | ||
}; | ||
} | ||
} | ||
|
||
module.exports = CredentialSignerVerifier; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters