Skip to content

refactor(account-lib): use Signature type #6389

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
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
1 change: 1 addition & 0 deletions .gitcommitscopes
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
account-lib
sdk-coin-ada
sdk-coin-rune
sdk-coin-sui
sdk-core
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { EIP191Message } from './eip191Message';
import { BaseCoin as CoinConfig } from '@bitgo/statics';
import { BaseMessageBuilder, BroadcastableMessage, IMessage, MessageStandardType } from '@bitgo/sdk-core';
import {
BaseMessageBuilder,
BroadcastableMessage,
deserializeSignatures,
IMessage,
MessageStandardType,
} from '@bitgo/sdk-core';

/**
* Builder for EIP-191 messages
Expand Down Expand Up @@ -48,14 +54,14 @@ export class Eip191MessageBuilder extends BaseMessageBuilder {
* @returns The parsed message
*/
public async fromBroadcastFormat(broadcastMessage: BroadcastableMessage): Promise<IMessage> {
const { type, payload, signatures, signers, metadata } = broadcastMessage;
const { type, payload, serializedSignatures, signers, metadata } = broadcastMessage;
if (type !== MessageStandardType.EIP191) {
throw new Error(`Invalid message type, expected ${MessageStandardType.EIP191}`);
}
return new EIP191Message({
coinConfig: this.coinConfig,
payload,
signatures,
signatures: deserializeSignatures(serializedSignatures),
signers,
metadata: {
...metadata,
Expand Down
34 changes: 26 additions & 8 deletions modules/abstract-eth/test/unit/messages/eip191/eip191Message.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import 'should';
import sinon from 'sinon';
import { MessageStandardType } from '@bitgo/sdk-core';
import { MessageStandardType, serializeSignatures } from '@bitgo/sdk-core';
import { fixtures } from '../fixtures';
import { EIP191Message } from '../../../../src';

Expand Down Expand Up @@ -84,17 +84,33 @@ describe('EIP191 Message', () => {
message.getSigners().should.containEql(fixtures.eip191.signer);

// Test adding new ones
message.addSignature('new-signature');
message.addSignature({
publicKey: { pub: 'pub1' },
signature: Buffer.from('new-signature'),
});
message.addSigner('new-signer');

message.getSignatures().should.containEql('new-signature');
message.getSignatures().should.containEql({
publicKey: { pub: 'pub1' },
signature: Buffer.from('new-signature'),
});
message.getSigners().should.containEql('new-signer');

// Test replacing all
message.setSignatures(['replaced-signature']);
message.setSignatures([
{
publicKey: { pub: 'pub2' },
signature: Buffer.from('replaced-signature'),
},
]);
message.setSigners(['replaced-signer']);

message.getSignatures().should.deepEqual(['replaced-signature']);
message.getSignatures().should.deepEqual([
{
publicKey: { pub: 'pub2' },
signature: Buffer.from('replaced-signature'),
},
]);
message.getSigners().should.deepEqual(['replaced-signer']);
});

Expand All @@ -121,12 +137,13 @@ describe('EIP191 Message', () => {

const broadcastFormat = await message.toBroadcastFormat();

const expectedSerializedSignatures = serializeSignatures([fixtures.eip191.signature]);
broadcastFormat.type.should.equal(MessageStandardType.EIP191);
broadcastFormat.payload.should.equal(fixtures.messages.validMessage);
broadcastFormat.signatures.should.deepEqual([fixtures.eip191.signature]);
broadcastFormat.serializedSignatures.should.deepEqual(expectedSerializedSignatures);
broadcastFormat.signers.should.deepEqual([fixtures.eip191.signer]);
broadcastFormat.metadata!.should.deepEqual(fixtures.eip191.metadata);
broadcastFormat.signablePayload!.should.equal('test-signable-payload');
broadcastFormat.signablePayload!.should.equal('dGVzdC1zaWduYWJsZS1wYXlsb2Fk');
});

it('should throw error when broadcasting without signatures', async () => {
Expand Down Expand Up @@ -164,10 +181,11 @@ describe('EIP191 Message', () => {

const broadcastString = await message.toBroadcastString();
const parsedBroadcast = JSON.parse(broadcastString);
const expectedSerializedSignatures = serializeSignatures([fixtures.eip191.signature]);

parsedBroadcast.type.should.equal(MessageStandardType.EIP191);
parsedBroadcast.payload.should.equal(fixtures.messages.validMessage);
parsedBroadcast.signatures.should.deepEqual([fixtures.eip191.signature]);
parsedBroadcast.serializedSignatures.should.deepEqual(expectedSerializedSignatures);
parsedBroadcast.signers.should.deepEqual([fixtures.eip191.signer]);
parsedBroadcast.metadata.should.deepEqual(fixtures.eip191.metadata);
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import 'should';
import sinon from 'sinon';
import { MessageStandardType } from '@bitgo/sdk-core';
import { BroadcastableMessage, MessageStandardType, serializeSignatures } from '@bitgo/sdk-core';
import { fixtures } from '../fixtures';
import { EIP191Message, Eip191MessageBuilder } from '../../../../src';

Expand Down Expand Up @@ -79,10 +79,10 @@ describe('EIP191 Message Builder', () => {
it('should reconstruct a message from broadcast format', async () => {
const builder = new Eip191MessageBuilder(fixtures.coin);

const broadcastMessage = {
const broadcastMessage: BroadcastableMessage = {
type: MessageStandardType.EIP191,
payload: fixtures.messages.validMessage,
signatures: [fixtures.eip191.signature],
serializedSignatures: serializeSignatures([fixtures.eip191.signature]),
signers: [fixtures.eip191.signer],
metadata: fixtures.eip191.metadata,
};
Expand All @@ -101,10 +101,10 @@ describe('EIP191 Message Builder', () => {
it('should throw an error for incorrect message type', async () => {
const builder = new Eip191MessageBuilder(fixtures.coin);

const broadcastMessage = {
const broadcastMessage: BroadcastableMessage = {
type: MessageStandardType.UNKNOWN,
payload: fixtures.messages.validMessage,
signatures: [fixtures.eip191.signature],
serializedSignatures: serializeSignatures([fixtures.eip191.signature]),
signers: [fixtures.eip191.signer],
metadata: {},
};
Expand Down
9 changes: 7 additions & 2 deletions modules/abstract-eth/test/unit/messages/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,13 @@ export const fixtures = {
},
eip191: {
validSignablePayload: '0x19457468657265756d205369676e6564204d6573736167653a0d48656c6c6f2c20776f726c6421',
signature:
'0x5d99b6f7f6d1f73d1a26497f2b1c89b24c0993913f86e9a2d02cd69887d9c94f3c880358579d811b21dd1b7fd9bb01c1d81d10e69f0384e675c32b39643be8921b',
signature: {
publicKey: { pub: '0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf' },
signature: Buffer.from(
'5d99b6f7f6d1f73d1a26497f2b1c89b24c0993913f86e9a2d02cd69887d9c94f3c880358579d811b21dd1b7fd9bb01c1d81d10e69f0384e675c32b39643be8921b',
'hex'
),
},
signer: '0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf',
metadata: {
encoding: 'utf8',
Expand Down
3 changes: 2 additions & 1 deletion modules/sdk-coin-ada/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@
"cbor": "^10.0.3",
"lodash": "^4.17.21",
"superagent": "^9.0.1",
"tweetnacl": "^1.0.3"
"tweetnacl": "^1.0.3",
"cbor-x": "^1.6.0"
},
"devDependencies": {
"@bitgo/sdk-api": "^1.64.1",
Expand Down
99 changes: 99 additions & 0 deletions modules/sdk-coin-ada/src/lib/messages/cip8/cip8Message.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { BaseMessage, MessageOptions, MessageStandardType, Signature } from '@bitgo/sdk-core';
import * as CardanoSL from '@emurgo/cardano-serialization-lib-nodejs';
import { constructCSLCoseObjects, coseObjectsOutputToBuffer, createCSLSigStructure } from './utils';
import { Encoder } from 'cbor-x';

/**
* Implementation of Message for CIP8 standard
*/
export class Cip8Message extends BaseMessage {
private readonly cborEncoder: Encoder = new Encoder({ mapsAsObjects: false });

constructor(options: MessageOptions) {
super({
...options,
type: MessageStandardType.CIP8,
});
}

/**
* Validates required fields and returns common setup objects
* @private
*/
private validateAndGetCommonSetup() {
if (!this.payload) {
throw new Error('Payload is required to build a CIP8 message');
}
if (!this.signers || this.signers.length === 0) {
throw new Error('A signer address is required to build a CIP8 message');
}

let cslAddress: CardanoSL.Address;
try {
cslAddress = CardanoSL.Address.from_bech32(this.signers[0]);
} catch (error) {
// Convert string errors to proper Error objects
if (typeof error === 'string') {
throw new Error(`Invalid signer address: ${error}`);
}
throw error;
}

const addressCborBytes = cslAddress.to_bytes();

return { addressCborBytes };
}

/**
* Returns the hash of the EIP-191 prefixed message
*/
async getSignablePayload(): Promise<string | Buffer> {
if (!this.signablePayload) {
this.signablePayload = this.buildSignablePayload();
}
return this.signablePayload;
}

/**
* Builds the signable payload for a CIP8 message
* @returns The signable payload as a Buffer
*/
buildSignablePayload(): string | Buffer {
const { addressCborBytes } = this.validateAndGetCommonSetup();
const { sigStructureCborBytes } = createCSLSigStructure(addressCborBytes, this.payload, this.cborEncoder);
return Buffer.from(sigStructureCborBytes);
}

getBroadcastableSignatures(): Signature[] {
if (!this.signatures.length) {
return [];
}

const signature = this.signatures[0].signature;
const publicKeyHex = this.signatures[0].publicKey.pub;

const { addressCborBytes } = this.validateAndGetCommonSetup();
const { protectedHeaderCborBytes, payloadBytes } = createCSLSigStructure(
addressCborBytes,
this.payload,
this.cborEncoder
);

const coseObjectsOutput = constructCSLCoseObjects(
protectedHeaderCborBytes,
payloadBytes,
signature,
CardanoSL.PublicKey.from_bytes(Buffer.from(publicKeyHex, 'hex')),
this.cborEncoder
);
const coseObjectsBuffer = coseObjectsOutputToBuffer(coseObjectsOutput, this.cborEncoder);
return [
{
signature: coseObjectsBuffer,
publicKey: {
pub: publicKeyHex,
},
},
];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { Cip8Message } from './cip8Message';
import { BaseCoin as CoinConfig } from '@bitgo/statics';
import {
BaseMessageBuilder,
BroadcastableMessage,
deserializeSignatures,
IMessage,
MessageStandardType,
} from '@bitgo/sdk-core';

/**
* Builder for EIP-191 messages
*/
export class Cip8MessageBuilder extends BaseMessageBuilder {
/**
* Base constructor.
* @param _coinConfig BaseCoin from statics library
*/
public constructor(_coinConfig: Readonly<CoinConfig>) {
super(_coinConfig, MessageStandardType.CIP8);
}

/**
* Build a signable message using the EIP-191 standard
* with previously set input and metadata
* @returns A signable message
*/
public async build(): Promise<IMessage> {
try {
if (!this.payload) {
throw new Error('Message payload must be set before building the message');
}
return new Cip8Message({
coinConfig: this.coinConfig,
payload: this.payload,
signatures: this.signatures,
signers: this.signers,
metadata: {
...this.metadata,
encoding: 'utf8',
},
});
} catch (err) {
if (err instanceof Error) {
throw err;
}
throw new Error('Failed to build EIP-191 message');
}
}

/**
* Parse a broadcastable message back into a message
* @param broadcastMessage The broadcastable message to parse
* @returns The parsed message
*/
public async fromBroadcastFormat(broadcastMessage: BroadcastableMessage): Promise<IMessage> {
const { type, payload, serializedSignatures, signers, metadata } = broadcastMessage;
if (type !== MessageStandardType.CIP8) {
throw new Error(`Invalid message type, expected ${MessageStandardType.CIP8}`);
}
return new Cip8Message({
coinConfig: this.coinConfig,
payload,
signatures: deserializeSignatures(serializedSignatures),
signers,
metadata: {
...metadata,
encoding: 'utf8',
},
});
}
}
2 changes: 2 additions & 0 deletions modules/sdk-coin-ada/src/lib/messages/cip8/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './cip8Message';
export * from './cip8MessageBuilder';
Loading
Loading