Implement setRecord and add test for setAutorityBond

This commit is contained in:
2022-04-21 09:44:24 +05:30
committed by Ashwin Phatak
parent 48507ca46a
commit 9de3c59c19
17 changed files with 901 additions and 53 deletions
+60 -2
View File
@@ -2,11 +2,17 @@ import assert from 'assert';
import BIP32Factory from 'bip32';
import * as ecc from 'tiny-secp256k1';
import * as bip39 from 'bip39';
import canonicalStringify from 'canonical-json';
import secp256k1 from 'secp256k1';
import { MessageTypes, signTypedData, SignTypedDataVersion } from '@metamask/eth-sig-util';
import { Ripemd160, Secp256k1 } from "@cosmjs/crypto";
import { toBech32 } from '@cosmjs/encoding';
import { fromHex, toBech32, toHex } from '@cosmjs/encoding';
import { rawSecp256k1PubkeyToRawAddress } from "@cosmjs/amino";
import { Payload, Signature } from './types';
import { sha256 } from 'js-sha256';
const AMINO_PREFIX = 'EB5AE98721';
const HDPATH = "m/44'/60'/0'/0";
const bip32 = BIP32Factory(ecc);
@@ -27,6 +33,8 @@ export class Account {
_publicKey?: Uint8Array
_cosmosAddress?: string
_formattedCosmosAddress?: string
_registryPublicKey?: string
_registryAddress?: string
/**
* Generate bip39 mnemonic.
@@ -67,6 +75,14 @@ export class Account {
return this._formattedCosmosAddress;
}
get registryPublicKey() {
return this._registryPublicKey;
}
get registryAddress() {
return this._registryAddress;
}
async init () {
// Generate public key.
const keypair = await Secp256k1.makeKeypair(this._privateKey);
@@ -75,11 +91,18 @@ export class Account {
this._publicKey = compressed
// 2. Generate cosmos-sdk address.
// let publicKeySha256 = sha256(this._publicKey);
this._cosmosAddress = new Ripemd160().update(keypair.pubkey).digest().toString();
// 3. Generate cosmos-sdk formatted address.
this._formattedCosmosAddress = toBech32('ethm', rawSecp256k1PubkeyToRawAddress(this._publicKey));
// 4. Generate registry formatted public key.
const publicKeyInHex = AMINO_PREFIX + toHex(this._publicKey);
this._registryPublicKey = Buffer.from(publicKeyInHex, 'hex').toString('base64');
// 5. Generate registry formatted address.
let publicKeySha256 = sha256(Buffer.from(publicKeyInHex, 'hex'));
this._registryAddress = new Ripemd160().update(fromHex(publicKeySha256)).digest().toString();
}
/**
@@ -89,6 +112,41 @@ export class Account {
return this._privateKey.toString('hex');
}
/**
* Get record signature.
* @param {object} record
*/
async signRecord(record: any) {
assert(record);
const recordAsJson = canonicalStringify(record);
// Double sha256.
const recordBytesToSign = Buffer.from(sha256(Buffer.from(sha256(Buffer.from(recordAsJson)), 'hex')), 'hex');
// Sign message
assert(recordBytesToSign);
const messageToSignSha256 = sha256(recordBytesToSign);
const messageToSignSha256InBytes = Buffer.from(messageToSignSha256, 'hex');
const sigObj = secp256k1.ecdsaSign(messageToSignSha256InBytes, this.privateKey);
return Buffer.from(sigObj.signature);
}
async signPayload(payload: Payload) {
assert(payload);
const { record } = payload;
const messageToSign = record.getMessageToSign();
const sig = await this.signRecord(messageToSign);
assert(this.registryPublicKey)
const signature = new Signature(this.registryPublicKey, sig.toString('base64'));
payload.addSignature(signature);
return signature;
}
/**
* Sign message.
*/
-2
View File
@@ -1,8 +1,6 @@
import { Registry } from './index';
import { getConfig } from './testing/helper';
const TX_WAIT_TIME = 5000; // in milliseconds.
const { chainId, restEndpoint, gqlEndpoint, privateKey, accountAddress, fee } = getConfig();
jest.setTimeout(90 * 1000);
+3
View File
@@ -0,0 +1,3 @@
declare module 'graphql.js'
declare module 'node-yaml'
declare module 'canonical-json'
-1
View File
@@ -1 +0,0 @@
declare module 'graphql.js'
+139 -3
View File
@@ -14,7 +14,8 @@ import { createTxMsgCancelBond, createTxMsgCreateBond, createTxMsgRefillBond, cr
import { RegistryClient } from "./registry-client";
import { Account } from "./account";
import { createTransaction } from "./txbuilder";
import { createTxMsgReserveAuthority, MessageMsgReserveAuthority } from './messages/nameservice';
import { createTxMsgReserveAuthority, createTxMsgSetAuthorityBond, createTxMsgSetName, createTxMsgSetRecord, MessageMsgReserveAuthority, MessageMsgSetAuthorityBond, MessageMsgSetName, MessageMsgSetRecord } from './messages/nameservice';
import { Payload, Record } from './types';
const DEFAULT_WRITE_ERROR = 'Unable to write to chiba-clonk.';
@@ -51,6 +52,7 @@ export class Registry {
path: [ 'submit' ]
}g
*/
console.error(error)
const message = JSON.parse(error.message);
return message.log || DEFAULT_WRITE_ERROR;
}
@@ -80,6 +82,28 @@ export class Registry {
return this._client.getAccount(address);
}
/**
* Publish record.
* @param transactionPrivateKey - private key in HEX to sign transaction.
*/
async setRecord(
params: { privateKey: string, record: any, bondId: string },
senderAddress: string,
transactionPrivateKey: string,
fee: Fee
) {
let result;
try {
result = await this._submitRecordTx(params, senderAddress, transactionPrivateKey, fee);
} catch (err: any) {
const error = err[0] || err;
throw new Error(Registry.processWriteError(error));
}
return parseTxResponse(result);
}
/**
* Send coins.
*/
@@ -247,7 +271,7 @@ export class Registry {
/**
* Reserve authority.
*/
async reserveAuthority(params: MessageMsgReserveAuthority, senderAddress: string, privateKey: string, fee: Fee) {
async reserveAuthority(params: MessageMsgReserveAuthority, senderAddress: string, privateKey: string, fee: Fee) {
let result;
try {
@@ -270,13 +294,125 @@ export class Registry {
return parseTxResponse(result);
}
/**
* Set authority bond.
* @param {string} name
* @param {string} bondId
* @param {string} privateKey
* @param {object} fee
*/
async setAuthorityBond(params: MessageMsgSetAuthorityBond, senderAddress: string, privateKey: string, fee: Fee) {
let result;
try {
const { account: { base_account: accountInfo } } = await this.getAccount(senderAddress);
const sender = {
accountAddress: accountInfo.address,
sequence: accountInfo.sequence,
accountNumber: accountInfo.account_number,
pubkey: accountInfo.pub_key.key,
}
const msg = createTxMsgSetAuthorityBond(this._chain, sender, fee, '', params)
result = await this._submitTx(msg, privateKey, sender);
} catch (err: any) {
const error = err[0] || err;
throw new Error(Registry.processWriteError(error));
}
return parseTxResponse(result);
}
/**
* Lookup authorities by names.
*/
async lookupAuthorities(names: string[], auction = false) {
async lookupAuthorities(names: string[], auction = false) {
return this._client.lookupAuthorities(names, auction);
}
/**
* Set name (WRN) to record ID (CID).
* @param {string} wrn
* @param {string} id
* @param {string} privateKey
* @param {object} fee
*/
async setName(params: MessageMsgSetName, senderAddress: string, privateKey: string, fee: Fee) {
let result;
try {
const { account: { base_account: accountInfo } } = await this.getAccount(senderAddress);
const sender = {
accountAddress: accountInfo.address,
sequence: accountInfo.sequence,
accountNumber: accountInfo.account_number,
pubkey: accountInfo.pub_key.key,
}
const msg = createTxMsgSetName(this._chain, sender, fee, '', params)
result = await this._submitTx(msg, privateKey, sender);
} catch (err: any) {
const error = err[0] || err;
throw new Error(Registry.processWriteError(error));
}
return parseTxResponse(result);
}
/**
* Submit record transaction.
* @param privateKey - private key in HEX to sign message.
* @param txPrivateKey - private key in HEX to sign transaction.
*/
async _submitRecordTx(
{ privateKey, record, bondId }: { privateKey: string, record: any, bondId: string },
senderAddress: string,
txPrivateKey: string,
fee: Fee
) {
if (!isKeyValid(privateKey)) {
throw new Error('Registry privateKey should be a hex string.');
}
if (!isKeyValid(bondId)) {
throw new Error(`Invalid bondId: ${bondId}.`);
}
// Sign record.
const recordSignerAccount = new Account(Buffer.from(privateKey, 'hex'));
await recordSignerAccount.init();
const registryRecord = new Record(record);
const payload = new Payload(registryRecord);
await recordSignerAccount.signPayload(payload);
// Send record payload Tx.
return this._submitRecordPayloadTx({ payload, bondId }, senderAddress, txPrivateKey, fee);
}
async _submitRecordPayloadTx(params: MessageMsgSetRecord, senderAddress: string, privateKey: string, fee: Fee) {
if (!isKeyValid(privateKey)) {
throw new Error('Registry privateKey should be a hex string.');
}
if (!isKeyValid(params.bondId)) {
throw new Error(`Invalid bondId: ${params.bondId}.`);
}
const { account: { base_account: accountInfo } } = await this.getAccount(senderAddress);
const sender = {
accountAddress: accountInfo.address,
sequence: accountInfo.sequence,
accountNumber: accountInfo.account_number,
pubkey: accountInfo.pub_key.key,
}
const msg = createTxMsgSetRecord(this._chain, sender, fee, '', params)
return this._submitTx(msg, privateKey, sender);
}
/**
* Submit a generic Tx to the chain.
*/
+238
View File
@@ -8,7 +8,9 @@ import {
} from '@tharsis/transactions'
import * as nameserviceTx from '../proto/vulcanize/nameservice/v1beta1/tx'
import * as nameservice from '../proto/vulcanize/nameservice/v1beta1/nameservice'
import { createTx } from './util'
import { Payload } from '../types'
const MSG_RESERVE_AUTHORITY_TYPES = {
MsgValue: [
@@ -18,11 +20,66 @@ const MSG_RESERVE_AUTHORITY_TYPES = {
],
}
const MSG_SET_NAME_TYPES = {
MsgValue: [
{ name: 'wrn', type: 'string' },
{ name: 'cid', type: 'string' },
{ name: 'signer', type: 'string' },
],
}
const MSG_SET_RECORD_TYPES = {
MsgValue: [
{ name: 'bond_id', type: 'string' },
{ name: 'signer', type: 'string' },
{ name: 'payload', type: 'TypePayload' },
],
TypePayload: [
{ name: 'record', type: 'TypePayloadRecord' },
{ name: 'signatures', type: 'TypePayloadSignatures[]' },
],
TypePayloadRecord: [
{ name: 'id', type: 'string' },
{ name: 'bond_id', type: 'string' },
{ name: 'create_time', type: 'string' },
{ name: 'expiry_time', type: 'string' },
{ name: 'deleted', type: 'bool' },
{ name: 'attributes', type: 'string' },
],
TypePayloadSignatures: [
{ name: 'sig', type: 'string' },
{ name: 'pub_key', type: 'string' }
],
}
const MSG_SET_AUTHORITY_BOND_TYPES = {
MsgValue: [
{ name: 'name', type: 'string' },
{ name: 'bond_id', type: 'string' },
{ name: 'signer', type: 'string' },
],
}
export interface MessageMsgReserveAuthority {
name: string
owner: string
}
export interface MessageMsgSetName {
wrn: string
cid: string
}
export interface MessageMsgSetRecord {
bondId: string
payload: Payload
}
export interface MessageMsgSetAuthorityBond {
name: string
bondId: string
}
export function createTxMsgReserveAuthority(
chain: Chain,
sender: Sender,
@@ -47,6 +104,78 @@ export function createTxMsgReserveAuthority(
return createTx(chain, sender, fee, memo, types, msg, msgCosmos)
}
export function createTxMsgSetName(
chain: Chain,
sender: Sender,
fee: Fee,
memo: string,
params: MessageMsgSetName,
) {
const types = generateTypes(MSG_SET_NAME_TYPES)
const msg = createMsgSetName(
params.wrn,
params.cid,
sender.accountAddress
)
const msgCosmos = protoCreateMsgSetName(
params.wrn,
params.cid,
sender.accountAddress
)
return createTx(chain, sender, fee, memo, types, msg, msgCosmos)
}
export function createTxMsgSetRecord(
chain: Chain,
sender: Sender,
fee: Fee,
memo: string,
params: MessageMsgSetRecord,
) {
const types = generateTypes(MSG_SET_RECORD_TYPES)
const msg = createMsgSetRecord(
params.bondId,
params.payload,
sender.accountAddress
)
const msgCosmos = protoCreateMsgSetRecord(
params.bondId,
params.payload,
sender.accountAddress
)
return createTx(chain, sender, fee, memo, types, msg, msgCosmos)
}
export function createTxMsgSetAuthorityBond(
chain: Chain,
sender: Sender,
fee: Fee,
memo: string,
params: MessageMsgSetAuthorityBond,
) {
const types = generateTypes(MSG_SET_AUTHORITY_BOND_TYPES)
const msg = createMsgSetAuthorityBond(
params.name,
params.bondId,
sender.accountAddress
)
const msgCosmos = protoCreateMsgSetAuthorityBond(
params.name,
params.bondId,
sender.accountAddress
)
return createTx(chain, sender, fee, memo, types, msg, msgCosmos)
}
function createMsgReserveAuthority(
name: string,
signer: string,
@@ -78,3 +207,112 @@ const protoCreateMsgReserveAuthority = (
path: 'vulcanize.nameservice.v1beta1.MsgReserveAuthority',
}
}
function createMsgSetName(
wrn: string,
cid: string,
signer: string
) {
return {
type: 'nameservice/SetName',
value: {
wrn,
cid,
signer
},
}
}
const protoCreateMsgSetName = (
wrn: string,
cid: string,
signer: string
) => {
const setNameMessage = new nameserviceTx.vulcanize.nameservice.v1beta1.MsgSetName({
wrn,
cid,
signer,
})
return {
message: setNameMessage,
path: 'vulcanize.nameservice.v1beta1.MsgSetName',
}
}
function createMsgSetRecord(
bondId: string,
payload: Payload,
signer: string
) {
return {
type: 'nameservice/SetRecord',
value: {
bond_id: bondId,
signer,
payload: payload.serialize()
},
}
}
const protoCreateMsgSetRecord = (
bondId: string,
payloadData: Payload,
signer: string
) => {
const record = new nameservice.vulcanize.nameservice.v1beta1.Record(payloadData.record.serialize())
const signatures = payloadData.signatures.map(
signature => new nameservice.vulcanize.nameservice.v1beta1.Signature(
signature.serialize()
)
)
const payload = new nameserviceTx.vulcanize.nameservice.v1beta1.Payload({
record,
signatures
})
const setNameMessage = new nameserviceTx.vulcanize.nameservice.v1beta1.MsgSetRecord({
bond_id: bondId,
signer,
payload
})
return {
message: setNameMessage,
path: 'vulcanize.nameservice.v1beta1.MsgSetRecord',
}
}
function createMsgSetAuthorityBond(
name: string,
bondId: string,
signer: string
) {
return {
type: 'nameservice/SetAuthorityBond',
value: {
name,
bond_id: bondId,
signer
},
}
}
const protoCreateMsgSetAuthorityBond = (
name: string,
bondId: string,
signer: string
) => {
const setAuthorityBondMessage = new nameserviceTx.vulcanize.nameservice.v1beta1.MsgSetAuthorityBond({
name,
bond_id: bondId,
signer,
})
return {
message: setAuthorityBondMessage,
path: 'vulcanize.nameservice.v1beta1.MsgSetAuthorityBond',
}
}
+35 -3
View File
@@ -1,8 +1,12 @@
import assert from 'assert';
import path from 'path';
import { Account } from './account';
import { Registry } from './index';
import { getConfig } from './testing/helper';
import { ensureUpdatedConfig, getConfig } from './testing/helper';
const WATCHER_ID = 'bafyreibmr47ksukoadck2wigevb2jp5j5oubfadeyzb6zi57ydjsvjmmby'
const WATCHER_YML_PATH = path.join(__dirname, './testing/data/watcher.yml');
jest.setTimeout(120 * 1000);
@@ -10,10 +14,11 @@ const { chainId, restEndpoint, gqlEndpoint, privateKey, accountAddress, fee } =
const namingTests = () => {
let registry: Registry;
let bondId: string;
let watcher: any;
let watcherId: string;
let authorityName: string;
let wrn: string;
beforeAll(async () => {
registry = new Registry(restEndpoint, gqlEndpoint, chainId);
@@ -21,6 +26,23 @@ const namingTests = () => {
// Create bond.
bondId = await registry.getNextBondId(accountAddress);
await registry.createBond({ denom: 'aphoton', amount: '1000000000' }, accountAddress, privateKey, fee);
// Create bot.
watcher = await ensureUpdatedConfig(WATCHER_YML_PATH);
const result = await registry.setRecord(
{
privateKey,
bondId,
record: watcher.record
},
accountAddress,
privateKey,
fee
)
// TODO: Get id from setRecord response.
// watcherId = result.data;
watcherId = WATCHER_ID;
});
test('Reserve authority.', async () => {
@@ -84,6 +106,16 @@ const namingTests = () => {
expect(record.ownerPublicKey).toBeDefined();
expect(Number(record.height)).toBeGreaterThan(0);
});
xtest('Set name for unbonded authority', async () => {
wrn = `wrn://${authorityName}/app/test`;
assert(watcherId)
await expect(registry.setName({ wrn, cid: watcherId }, accountAddress, privateKey, fee)).rejects.toThrow('Authority bond not found.');
});
test('Set authority bond', async () => {
await registry.setAuthorityBond({ name: authorityName, bondId }, accountAddress, privateKey, fee);
});
};
if (process.env.AUCTIONS_ENABLED) {
@@ -311,11 +311,11 @@ export namespace vulcanize.nameservice.v1beta1 {
constructor(data?: any[] | {
id?: string;
bond_id?: string;
create_time?: dependency_2.google.protobuf.Timestamp;
expiry_time?: dependency_2.google.protobuf.Timestamp;
create_time?: string;
expiry_time?: string;
deleted?: boolean;
owners?: string[];
attributes?: Uint8Array;
attributes?: string;
}) {
super();
pb_1.Message.initialize(this, Array.isArray(data) ? data : [], 0, -1, [6], []);
@@ -356,16 +356,16 @@ export namespace vulcanize.nameservice.v1beta1 {
pb_1.Message.setField(this, 2, value);
}
get create_time() {
return pb_1.Message.getWrapperField(this, dependency_2.google.protobuf.Timestamp, 3) as dependency_2.google.protobuf.Timestamp;
return pb_1.Message.getField(this, 3) as string;
}
set create_time(value: dependency_2.google.protobuf.Timestamp) {
pb_1.Message.setWrapperField(this, 3, value);
set create_time(value: string) {
pb_1.Message.setField(this, 3, value);
}
get expiry_time() {
return pb_1.Message.getWrapperField(this, dependency_2.google.protobuf.Timestamp, 4) as dependency_2.google.protobuf.Timestamp;
return pb_1.Message.getField(this, 4) as string;
}
set expiry_time(value: dependency_2.google.protobuf.Timestamp) {
pb_1.Message.setWrapperField(this, 4, value);
set expiry_time(value: string) {
pb_1.Message.setField(this, 4, value);
}
get deleted() {
return pb_1.Message.getField(this, 5) as boolean;
@@ -380,19 +380,19 @@ export namespace vulcanize.nameservice.v1beta1 {
pb_1.Message.setField(this, 6, value);
}
get attributes() {
return pb_1.Message.getField(this, 7) as Uint8Array;
return pb_1.Message.getField(this, 7) as string;
}
set attributes(value: Uint8Array) {
set attributes(value: string) {
pb_1.Message.setField(this, 7, value);
}
static fromObject(data: {
id?: string;
bond_id?: string;
create_time?: ReturnType<typeof dependency_2.google.protobuf.Timestamp.prototype.toObject>;
expiry_time?: ReturnType<typeof dependency_2.google.protobuf.Timestamp.prototype.toObject>;
create_time?: string;
expiry_time?: string;
deleted?: boolean;
owners?: string[];
attributes?: Uint8Array;
attributes?: string;
}) {
const message = new Record({});
if (data.id != null) {
@@ -402,10 +402,10 @@ export namespace vulcanize.nameservice.v1beta1 {
message.bond_id = data.bond_id;
}
if (data.create_time != null) {
message.create_time = dependency_2.google.protobuf.Timestamp.fromObject(data.create_time);
message.create_time = data.create_time;
}
if (data.expiry_time != null) {
message.expiry_time = dependency_2.google.protobuf.Timestamp.fromObject(data.expiry_time);
message.expiry_time = data.expiry_time;
}
if (data.deleted != null) {
message.deleted = data.deleted;
@@ -422,11 +422,11 @@ export namespace vulcanize.nameservice.v1beta1 {
const data: {
id?: string;
bond_id?: string;
create_time?: ReturnType<typeof dependency_2.google.protobuf.Timestamp.prototype.toObject>;
expiry_time?: ReturnType<typeof dependency_2.google.protobuf.Timestamp.prototype.toObject>;
create_time?: string;
expiry_time?: string;
deleted?: boolean;
owners?: string[];
attributes?: Uint8Array;
attributes?: string;
} = {};
if (this.id != null) {
data.id = this.id;
@@ -435,10 +435,10 @@ export namespace vulcanize.nameservice.v1beta1 {
data.bond_id = this.bond_id;
}
if (this.create_time != null) {
data.create_time = this.create_time.toObject();
data.create_time = this.create_time;
}
if (this.expiry_time != null) {
data.expiry_time = this.expiry_time.toObject();
data.expiry_time = this.expiry_time;
}
if (this.deleted != null) {
data.deleted = this.deleted;
@@ -459,16 +459,16 @@ export namespace vulcanize.nameservice.v1beta1 {
writer.writeString(1, this.id);
if (typeof this.bond_id === "string" && this.bond_id.length)
writer.writeString(2, this.bond_id);
if (this.create_time !== undefined)
writer.writeMessage(3, this.create_time, () => this.create_time.serialize(writer));
if (this.expiry_time !== undefined)
writer.writeMessage(4, this.expiry_time, () => this.expiry_time.serialize(writer));
if (typeof this.create_time === "string" && this.create_time.length)
writer.writeString(3, this.create_time);
if (typeof this.expiry_time === "string" && this.expiry_time.length)
writer.writeString(4, this.expiry_time);
if (this.deleted !== undefined)
writer.writeBool(5, this.deleted);
if (this.owners !== undefined)
writer.writeRepeatedString(6, this.owners);
if (this.attributes !== undefined)
writer.writeBytes(7, this.attributes);
if (typeof this.attributes === "string" && this.attributes.length)
writer.writeString(7, this.attributes);
if (!w)
return writer.getResultBuffer();
}
@@ -485,10 +485,10 @@ export namespace vulcanize.nameservice.v1beta1 {
message.bond_id = reader.readString();
break;
case 3:
reader.readMessage(message.create_time, () => message.create_time = dependency_2.google.protobuf.Timestamp.deserialize(reader));
message.create_time = reader.readString();
break;
case 4:
reader.readMessage(message.expiry_time, () => message.expiry_time = dependency_2.google.protobuf.Timestamp.deserialize(reader));
message.expiry_time = reader.readString();
break;
case 5:
message.deleted = reader.readBool();
@@ -497,7 +497,7 @@ export namespace vulcanize.nameservice.v1beta1 {
pb_1.Message.addToRepeatedField(message, 6, reader.readString());
break;
case 7:
message.attributes = reader.readBytes();
message.attributes = reader.readString();
break;
default: reader.skipField();
}
+5
View File
@@ -0,0 +1,5 @@
{
"$schema": "http://json-schema.org/schema#",
"id": "/Record",
"type": "object"
}
+6
View File
@@ -0,0 +1,6 @@
record:
type: watcher
name: ERC20 Watcher
version: 1.0.0
protocol:
/: QmdeazkS38aCrqG6qKwaio2fQnShE6RGpmNdqStLkkZcQN
+10
View File
@@ -1,4 +1,14 @@
import assert from 'assert';
import yaml from 'node-yaml';
import semver from 'semver';
export const ensureUpdatedConfig = async (path: string) => {
const conf = await yaml.read(path);
conf.record.version = semver.inc(conf.record.version, 'patch');
await yaml.write(path, conf);
return conf;
};
export const getConfig = () => {
assert(process.env.PRIVATE_KEY);
+132
View File
@@ -0,0 +1,132 @@
import assert from 'assert';
import { Validator } from 'jsonschema';
import RecordSchema from './schema/record.json';
import { Util } from './util';
/**
* Record.
*/
export class Record {
_record: any
/**
* New Record.
*/
constructor(record: any) {
assert(record);
const validator = new Validator();
const result = validator.validate(record, RecordSchema);
if (!result.valid) {
result.errors.map(console.error);
throw new Error('Invalid record input.');
}
this._record = record;
}
get attributes() {
return Buffer.from(JSON.stringify(this._record), 'binary').toString('base64')
}
/**
* Serialize record.
*/
serialize() {
// return Util.sortJSON({
// });
return {
'id': '_',
'bond_id': '_',
'create_time': '_',
'expiry_time': '_',
'deleted': true,
'attributes': this.attributes,
// 'owners': [],
}
}
/**
* Get message to calculate record signature.
*/
getMessageToSign() {
return Util.sortJSON(this._record);
}
}
/**
* Record Signature.
*/
export class Signature {
_pubKey: string
_sig: string
/**
* New Signature.
*/
constructor(pubKey: string, sig: string) {
assert(pubKey);
assert(sig);
this._pubKey = pubKey;
this._sig = sig;
}
/**
* Serialize Signature.
*/
serialize() {
return Util.sortJSON({
'pub_key': this._pubKey,
'sig': this._sig
});
}
}
/**
* Message Payload.
*/
export class Payload {
_record: Record
_signatures: Signature[]
/**
* New Payload.
*/
constructor(record: Record, ...signatures: Signature[]) {
assert(record);
this._record = record;
this._signatures = signatures;
}
get record() {
return this._record;
}
get signatures() {
return this._signatures;
}
/**
* Add message signature to payload.
*/
addSignature(signature: any) {
assert(signature);
this._signatures.push(signature);
}
/**
* Serialize Payload.
*/
serialize() {
// return Util.sortJSON({
// });
return {
'record': this._record.serialize(),
'signatures': this._signatures.map(s => s.serialize())
}
}
}
+13
View File
@@ -1,3 +1,5 @@
import dagCBOR from 'ipld-dag-cbor';
/**
* Utils
*/
@@ -76,4 +78,15 @@ export class Util {
return res;
}
/**
* Get record content ID.
*/
static async getContentId(record: any) {
console.log(record)
const content = dagCBOR.util.serialize(record);
const cid = await dagCBOR.util.cid(content);
return cid.toString();
}
}