Commit 957bac7d authored by James's avatar James
Browse files

updated new package

parent f702d9f9
//registry.npmjs.org/:_authToken=${NPM_TOKEN}
//registry.npmjs.org/:_authToken=ac25ca83-a6a1-4d3b-89c4-db28e03b7872
......@@ -13,6 +13,7 @@ export enum SupportedChains {
Ethmain = 'ethmain',
Ethropst = 'ethropst',
Ethrinkeby = 'ethrinkeby',
Ethbloxberg = 'ethbloxberg',
Mocknet = 'mocknet',
Regtest = 'regtest',
Testnet = 'testnet'
......@@ -24,6 +25,7 @@ export enum TRANSACTION_APIS {
blockexplorer = 'blockexplorer',
blockstream = 'blockstream',
etherscan = 'etherscan',
blockscout = 'blockscout'
}
export type TExplorerParsingFunction = ((jsonResponse, chain?: SupportedChains) => TransactionData) | ((jsonResponse, chain?: SupportedChains) => Promise<TransactionData>);
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
{
"name": "@blockcerts/cert-verifier-js",
"version": "0.0.0-dev",
"description": "Javascript library for verifying Blockcerts",
"name": "@bloxberg-org/cert-verifier-js",
"version": "0.0.3",
"description": "Javascript library for verifying bloxberg Certificates",
"repository": {
"type": "git",
"url": "https://github.com/blockchain-certificates/cert-verifier-js.git"
"url": "https://github.com/bloxberg-org/cert-verifier-js.git"
},
"main": "dist/verifier.js",
"module": "dist/verifier-es.js",
......@@ -32,7 +32,7 @@
"publishConfig": {
"access": "public"
},
"author": "Kim Duffy",
"author": "James Lawton",
"license": "MIT",
"semistandard": {
"globals": [
......@@ -53,7 +53,7 @@
"dependencies": {
"@babel/runtime": "^7.11.2",
"@babel/runtime-corejs3": "^7.8.7",
"@vaultie/lds-merkle-proof-2019": "0.0.8",
"@vaultie/lds-merkle-proof-2019": "https://github.com/crossoveranx/lds-merkle-proof-2019.git",
"bigi": "^1.4.2",
"bitcoinjs-lib": "^5.0.3",
"bs58": "^4.0.1",
......@@ -62,6 +62,7 @@
"debug": "^4.1.1",
"ecurve": "^1.0.4",
"elliptic": "github:xg-wang/elliptic",
"ethereum-input-data-decoder": "^0.3.1",
"jsonld": "^1.8.1",
"sha256": "^0.2.0",
"xmlhttprequest": "^1.8.0"
......
......@@ -3,7 +3,8 @@ enum TRANSACTION_APIS {
blockcypher = 'blockcypher',
blockexplorer = 'blockexplorer',
blockstream = 'blockstream',
etherscan = 'etherscan'
etherscan = 'etherscan',
blockscout = 'blockscout'
}
const TRANSACTION_ID_PLACEHOLDER = '{transaction_id}';
......
......@@ -5,6 +5,7 @@ export enum SupportedChains {
Ethmain = 'ethmain',
Ethropst = 'ethropst',
Ethrinkeby = 'ethrinkeby',
Ethbloxberg = 'ethbloxberg',
Mocknet = 'mocknet',
Regtest = 'regtest',
Testnet = 'testnet'
......@@ -61,6 +62,16 @@ const BLOCKCHAINS: {[chain in SupportedChains]: IBlockchainObject} = {
raw: `https://rinkeby.etherscan.io/getRawTx?tx=${TRANSACTION_ID_PLACEHOLDER}`
}
},
[SupportedChains.Ethbloxberg]: {
code: SupportedChains.Ethbloxberg,
name: 'bloxberg',
//prefixes: ['0x'],
signatureValue: 'ethereumBloxberg',
transactionTemplates: {
full: `https://blockexplorer.bloxberg.org/tx/${TRANSACTION_ID_PLACEHOLDER}`,
raw: `https://blockexplorer.bloxberg.org/api/api?module=transaction&action=gettxinfo&txhash=${TRANSACTION_ID_PLACEHOLDER}`
}
},
[SupportedChains.Mocknet]: {
code: SupportedChains.Mocknet,
name: 'Mocknet',
......
......@@ -13,12 +13,13 @@ const language = {
label: 'Hash comparison',
labelPending: 'Comparing hash',
subSteps: []
},
[statusCheck]: {
label: 'Status check',
labelPending: 'Checking record status',
subSteps: []
}
// [statusCheck]: {
// label: 'Status check',
// labelPending: 'Checking record status',
// subSteps: []
// }
};
export { final, formatValidation, hashComparison, statusCheck, language };
//export { final, formatValidation, hashComparison, statusCheck, language };
export { final, formatValidation, hashComparison, language };
......@@ -11,8 +11,8 @@ const checkMerkleRoot = 'checkMerkleRoot';
const checkReceipt = 'checkReceipt';
const checkIssuerSignature = 'checkIssuerSignature';
const checkAuthenticity = 'checkAuthenticity';
const checkRevokedStatus = 'checkRevokedStatus';
const checkExpiresDate = 'checkExpiresDate';
//const checkRevokedStatus = 'checkRevokedStatus';
//const checkExpiresDate = 'checkExpiresDate';
function getTextFor (subStep, status) {
return i18n['en-US'].subSteps[`${subStep}${status}`];
......@@ -23,8 +23,8 @@ const LABEL_PENDING = 'LabelPending';
const subStepsMap = {
[STEPS.formatValidation]: [getTransactionId, computeLocalHash, fetchRemoteHash, getIssuerProfile, parseIssuerKeys],
[STEPS.hashComparison]: [compareHashes, checkMerkleRoot, checkReceipt],
[STEPS.statusCheck]: [checkIssuerSignature, checkAuthenticity, checkRevokedStatus, checkExpiresDate]
[STEPS.hashComparison]: [compareHashes, checkMerkleRoot, checkReceipt, checkIssuerSignature]
//[STEPS.statusCheck]: [checkIssuerSignature, checkAuthenticity, checkRevokedStatus, checkExpiresDate]
};
function generateSubsteps (parentKey) {
......@@ -53,8 +53,8 @@ export {
checkMerkleRoot,
checkReceipt,
checkIssuerSignature,
checkAuthenticity,
checkRevokedStatus,
checkExpiresDate,
//checkAuthenticity,
//checkRevokedStatus,
//checkExpiresDate,
language
};
......@@ -14,10 +14,10 @@ const versionVerificationMap = {
SUB_STEPS.parseIssuerKeys,
SUB_STEPS.compareHashes,
SUB_STEPS.checkMerkleRoot,
SUB_STEPS.checkReceipt,
SUB_STEPS.checkRevokedStatus,
SUB_STEPS.checkAuthenticity,
SUB_STEPS.checkExpiresDate
SUB_STEPS.checkReceipt
//SUB_STEPS.checkRevokedStatus,
//SUB_STEPS.checkAuthenticity
//SUB_STEPS.checkExpiresDate
],
[NETWORKS.testnet]: [
SUB_STEPS.computeLocalHash,
......
......@@ -22,6 +22,7 @@ export function getExplorersByChain (chain: SupportedChains, certificateVersion:
case BLOCKCHAINS[SupportedChains.Ethmain].code:
case BLOCKCHAINS[SupportedChains.Ethropst].code:
case BLOCKCHAINS[SupportedChains.Ethrinkeby].code:
case BLOCKCHAINS[SupportedChains.Ethbloxberg].code:
return explorerAPIs.ethereum;
}
......
import { ExplorerAPI } from '../../certificate';
import { ExplorerURLs } from '../../index';
import { TRANSACTION_APIS, TRANSACTION_ID_PLACEHOLDER } from '../../constants/api';
import { TransactionData } from '../../models/TransactionData';
import { dateToUnixTimestamp } from '../../helpers/date';
import { BLOCKCHAINS, CONFIG, SUB_STEPS } from '../../constants';
import { VerifierError } from '../../models';
import { stripHashPrefix } from '../utils/stripHashPrefix';
import { getText } from '../../domain/i18n/useCases';
import { prependHashPrefix } from '../utils/prependHashPrefix';
import InputDataDecoder from 'ethereum-input-data-decoder';
import { request } from '../../services/request';
import {SupportedChains} from "../../constants/blockchains";
const serviceURL: ExplorerURLs = {
main: `https://blockexplorer.bloxberg.org/api/api?module=transaction&action=gettxinfo&txhash=${TRANSACTION_ID_PLACEHOLDER}`,
test: `https://blockexplorer.bloxberg.org/api/api?module=transaction&action=gettxinfo&txhash=${TRANSACTION_ID_PLACEHOLDER}`
};
async function parsingFunction (jsonResponse): Promise<TransactionData> {
const smartContractAddress = jsonResponse.result.to
console.log("smartContractAddress: " + smartContractAddress)
const smartContractLink = "https://blockexplorer.bloxberg.org/api/api?module=contract&action=getabi&address=" + smartContractAddress
//TBD: Best option is to call blockexplorer API and get the ABI. Importing for simplicity...
async function getSmartContractABI (smartContractLink): Promise<any> {
try {
const response = await request({url: smartContractLink});
const responseData = JSON.parse(response);
console.log(responseData)
const abiData = responseData.result;
const abiDataJson = JSON.parse(abiData)
console.log(abiDataJson)
return abiDataJson
}
catch (err) {
throw new VerifierError(SUB_STEPS.fetchRemoteHash, getText('errors', 'unableToGetRemoteHash'));
}
}
function parseSmartContractData(abi, jsonResponse) {
try {
console.log(abi)
const decoder = new InputDataDecoder(abi);
const data = jsonResponse.result.input
const result = decoder.decodeData(data);
var remoteHash = result.inputs[2]
console.log("remote: " + remoteHash)
} catch (err) {
console.log(err)
}
const time = jsonResponse.result.timeStamp
let issuingAddress = jsonResponse.result.from;
console.log(remoteHash)
//const remoteHash = stripHashPrefix(jsonResponse.result.hash, BLOCKCHAINS.ethmain.prefixes);
console.log("blockscout remote merkle root: " + remoteHash)
issuingAddress = prependHashPrefix(issuingAddress, BLOCKCHAINS.ethmain.prefixes);
const revokedAddresses = []
console.log(issuingAddress)
console.log(remoteHash)
console.log(time)
return {
remoteHash,
issuingAddress,
time,
revokedAddresses
};
}
const abiResponse = await getSmartContractABI(smartContractLink);
return parseSmartContractData(abiResponse, jsonResponse)
}
export const explorerApi: ExplorerAPI = {
serviceURL,
serviceName: TRANSACTION_APIS.blockscout,
parsingFunction,
priority: -1
};
......@@ -8,6 +8,7 @@ import { TransactionData } from '../models/TransactionData';
import { ExplorerAPI } from '../certificate';
import { explorerApi as EtherscanApi } from './ethereum/etherscan';
import { explorerApi as BlockCypherETHApi } from './ethereum/blockcypher';
import { explorerApi as BlockscoutApi } from './ethereum/blockscout';
import { explorerApi as BlockExplorerApi } from './bitcoin/blockexplorer';
import { explorerApi as BlockstreamApi } from './bitcoin/blockstream';
import { explorerApi as BlockCypherBTCApi } from './bitcoin/blockcypher';
......@@ -58,7 +59,8 @@ const BitcoinTransactionAPIArray = [
const EthereumTransactionAPIArray = [
EtherscanApi,
BlockCypherETHApi
BlockCypherETHApi,
BlockscoutApi
];
const BlockchainExplorersWithSpentOutputInfo = [
......
......@@ -13,6 +13,7 @@ export enum SupportedChains {
Ethmain = 'ethmain',
Ethropst = 'ethropst',
Ethrinkeby = 'ethrinkeby',
Ethbloxberg = 'ethbloxberg',
Mocknet = 'mocknet',
Regtest = 'regtest',
Testnet = 'testnet'
......@@ -24,6 +25,7 @@ export enum TRANSACTION_APIS {
blockexplorer = 'blockexplorer',
blockstream = 'blockstream',
etherscan = 'etherscan',
blockscout = 'blockscout'
}
export type TExplorerParsingFunction = ((jsonResponse, chain?: SupportedChains) => TransactionData) | ((jsonResponse, chain?: SupportedChains) => Promise<TransactionData>);
......
import { BLOCKCHAINS, Certificate, CERTIFICATE_VERSIONS } from '../../../src';
import FIXTURES from '../../fixtures';
import signatureAssertion from '../../assertions/v3.0-alpha-learningmachine-signature-merkle2019';
import issuerProfileAssertion from '../../assertions/v3.0-alpha-issuer-profile';
import verificationStepsV3 from '../../assertions/verification-steps-v3';
const assertionTransactionId = '0xc46050a5cc05cc6a7c9588598960253a3827c335f89414e3c1d8016fd639d73d';
describe('Certificate entity test suite', function () {
const fixture = FIXTURES.BlockcertsBloxberg;
describe('constructor method', function () {
describe('given it is called with valid v3 certificate data bloxberg', function () {
let certificate;
beforeEach(async function () {
certificate = new Certificate(fixture);
await certificate.init();
});
afterEach(function () {
certificate = null;
});
it('should set version to the certificate object', function () {
expect(certificate.version).toBe(CERTIFICATE_VERSIONS.V3_0_alpha);
});
it('should set the decoded signature as the receipt to the certificate object', function () {
expect(certificate.receipt).toEqual(signatureAssertion);
});
it('should set the transactionId to the certificate object', function () {
expect(certificate.transactionId).toEqual(assertionTransactionId);
});
it('should set the chain property', function () {
expect(certificate.chain).toEqual(BLOCKCHAINS.ethbloxberg);
});
it('should set the expires property', function () {
expect(certificate.expires).toEqual(fixture.expirationDate);
});
it('should set the metadataJson property', function () {
expect(certificate.metadataJson).toEqual(fixture.metadataJson);
});
it('should set the issuer property', function () {
expect(certificate.issuer).toEqual(issuerProfileAssertion);
});
it('should set the issuedOn property', function () {
expect(certificate.issuedOn).toEqual(fixture.issuanceDate);
});
it('should set the id property', function () {
expect(certificate.id).toEqual(fixture.id);
});
it('should set the recordLink property', function () {
expect(certificate.recordLink).toEqual(fixture.id);
});
it('should set the recipientFullName property', function () {
expect(certificate.recipientFullName).toEqual(fixture.credentialSubject.name);
});
it('should set the rawTransactionLink property', function () {
const rawTransactionLinkAssertion = `https://blockexplorer.bloxberg.org/tx${assertionTransactionId}`;
expect(certificate.rawTransactionLink).toEqual(rawTransactionLinkAssertion);
});
it('should set the transactionLink property', function () {
const transactionLinkAssertion = `https://blockexplorer.bloxberg.org/tx${assertionTransactionId}`;
expect(certificate.transactionLink).toEqual(transactionLinkAssertion);
});
it('should set the verificationSteps property', function () {
expect(certificate.verificationSteps).toEqual(verificationStepsV3);
});
});
describe('retrieving the issuer profile - failing cases', function () {
describe('when the issuer profile is undefined', function () {
it('should throw an error', async function () {
const failingFixture = JSON.parse(JSON.stringify(fixture));
delete failingFixture.issuer;
const certificate = new Certificate(failingFixture);
await expect(certificate.init())
.rejects
.toThrow(/^Error: Unable to get issuer profile - no issuer address given$/);
});
});
describe('when the issuer profile is null', function () {
it('should throw an error', async function () {
const failingFixture = JSON.parse(JSON.stringify(fixture));
failingFixture.issuer = null;
const certificate = new Certificate(failingFixture);
await expect(certificate.init())
.rejects
.toThrow(/^Error: Unable to get issuer profile - no issuer address given$/);
});
});
describe('when the issuer profile is an empty string', function () {
it('should throw an error', async function () {
const failingFixture = JSON.parse(JSON.stringify(fixture));
failingFixture.issuer = '';
const certificate = new Certificate(failingFixture);
await expect(certificate.init())
.rejects
.toThrow(/^Error: Unable to get issuer profile - no issuer address given$/);
});
});
describe('when the issuer profile is not a valid URL', function () {
it('should throw an error', async function () {
const failingFixture = JSON.parse(JSON.stringify(fixture));
failingFixture.issuer = 'this is not a URL';
const certificate = new Certificate(failingFixture);
await expect(certificate.init())
.rejects
.toThrow(/^Error: Unable to get issuer profile - no issuer address given$/);
});
});
describe('when the issuer profile URL yields a server error', function () {
it('should throw an error', async function () {
const failingFixture = JSON.parse(JSON.stringify(fixture));
failingFixture.issuer += 'willfailfortests';
const certificate = new Certificate(failingFixture);
await expect(certificate.init())
.rejects
.toThrow(/^Error: Unable to get issuer profile$/);
});
});
describe('when the issuer profile URL is not of a issuer profile', function () {
it('should throw an error', async function () {
const failingFixture = JSON.parse(JSON.stringify(fixture));
failingFixture.issuer = 'https://raw.githubusercontent.com/blockchain-certificates/cert-schema/master/cert_schema/3.0-alpha/context.json';
const certificate = new Certificate(failingFixture);
await expect(certificate.init())
.rejects
.toThrow(/^Error: Unable to get issuer profile - retrieved file does not seem to be a valid profile$/);
});
});
});
});
});
import { Certificate, STEPS, SUB_STEPS, VERIFICATION_STATUSES } from '../../../src';
import sinon from 'sinon';
import * as Explorers from '../../../src/explorers/explorer';
import FIXTURES from '../../fixtures';
import domain from '../../../src/domain';
import etherscanApiWithKey from '../../data/etherscan-key';
describe('Certificate test suite', function () {
describe('verify method', function () {
describe('given it is called with a Blockcerts v3', function () {
describe('when the certificate is valid', function () {
let certificate;
beforeEach(async function () {
sinon.stub(Explorers, 'getTransactionFromApi').resolves({
remoteHash: '98bd471b266ab285238fa36d33c7ed8215e99f413fa5742937f45ad54a83b555',
issuingAddress: '0xD748BF41264b906093460923169643f45BDbC32e',
time: '2020-09-23T14:36:32.438820+00:00',
revokedAddresses: []
});
certificate = new Certificate(FIXTURES.BlockcertsBloxberg);
await certificate.init();
});
afterEach(function () {
certificate = null;
sinon.restore();
});
it('should call it with the step, the text and the status', async function () {
const callbackSpy = sinon.spy();
const assertionStep = {
code: SUB_STEPS.getTransactionId,
label: SUB_STEPS.language.getTransactionId.labelPending,
status: VERIFICATION_STATUSES.SUCCESS
};
await certificate.verify(callbackSpy);
expect(callbackSpy.calledWith(sinon.match(assertionStep))).toBe(true);
});
it('should return the success finalStep', async function () {
const successMessage = domain.i18n.getText('success', 'blockchain');
const expectedFinalStep = {
code: STEPS.final,
status: VERIFICATION_STATUSES.SUCCESS,
message: successMessage
};
const finalStep = await certificate.verify();
expect(finalStep).toEqual(expectedFinalStep);
});
});
describe('when the certificate has been tampered with', function () {
let certificate;
beforeEach(async function () {
sinon.stub(Explorers, 'getTransactionFromApi').resolves({
remoteHash: 'a16e3677d1f0ddae82642b6995937d3082fdef3323431cf6d0ada4acb893f4cc',
issuingAddress: '0x7e30a37763e6ba1ffede1750bbefb4c60b17a1b3',
time: '2020-02-04T13:52:09.000Z',
revokedAddresses: []
});
certificate = new Certificate(FIXTURES.BlockcertsV3AlphaTampered, { explorerAPIs: [etherscanApiWithKey] });
await certificate.init();
});
afterEach(function () {
sinon.restore();
certificate = null;
});
it('should return the error finalStep', async function () {
const errorMessage = domain.i18n.getText('errors', 'ensureHashesEqual');
const expectedFinalStep = {
code: STEPS.final,
status: VERIFICATION_STATUSES.FAILURE,
message: errorMessage
};
const finalStep = await certificate.verify();
expect(finalStep).toEqual(expectedFinalStep);
});
});
});
describe('given it is called with a Blockcerts v3 with custom contexts', function () {
describe('when the certificate is valid', function () {
let certificate;
beforeEach(async function () {
sinon.stub(Explorers, 'getTransactionFromApi').resolves({
remoteHash: 'de1ddd816629c5aaecbaae7ad8ad193a9524362f8c98508bf891f2df3a8359e4',
issuingAddress: '0x7e30a37763e6ba1ffede1750bbefb4c60b17a1b3',
time: '2020-03-23T20:38:13.000Z',
revokedAddresses: []
});
certificate = new Certificate(FIXTURES.BlockcertsV3AlphaCustomContext, { explorerAPIs: [etherscanApiWithKey] });
await certificate.init();
});
afterEach(function () {
sinon.restore();
certificate = null;
});
it('should call it with the step, the text and the status', async function () {
const callbackSpy = sinon.spy();
const assertionStep = {
code: SUB_STEPS.getTransactionId,
label: SUB_STEPS.language.getTransactionId.labelPending,
status: VERIFICATION_STATUSES.SUCCESS
};
await certificate.verify(callbackSpy);
expect(callbackSpy.calledWith(sinon.match(assertionStep))).toBe(true);
});
it('should return the success finalStep', async function () {
const successMessage = domain.i18n.getText('success', 'blockchain');
const expectedFinalStep = {
code: STEPS.final,
status: VERIFICATION_STATUSES.SUCCESS,
message: successMessage
};
const finalStep = await certificate.verify();
expect(finalStep).toEqual(expectedFinalStep);
});
});
});