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
3 changes: 3 additions & 0 deletions modules/sdk-coin-canton/src/lib/constant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,6 @@ export const PUBLIC_KEY_FORMAT = 'CRYPTO_KEY_FORMAT_RAW';
export const PUBLIC_KEY_SPEC = 'SIGNING_KEY_SPEC_EC_CURVE25519';
export const SIGNATURE_FORMAT = 'SIGNATURE_FORMAT_RAW';
export const SIGNATURE_ALGORITHM_SPEC = 'SIGNING_ALGORITHM_SPEC_ED25519';
export const HASHING_SCHEME_VERSION = 'HASHING_SCHEME_VERSION_V2';

export const DUMMY_HASH = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=';
32 changes: 32 additions & 0 deletions modules/sdk-coin-canton/src/lib/iface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,42 @@ export interface WalletInitBroadcastData {
multiHashSignatures: MultiHashSignature[];
}

export interface PartySignature {
party: string;
signatures: MultiHashSignature[];
}

export interface TransactionBroadcastData {
prepareCommandResponse?: CantonPrepareCommandResponse;
txType: string;
preparedTransaction?: string;
partySignatures?: {
signatures: PartySignature[];
};
deduplicationPeriod?: {
Empty: Record<string, never>;
};
submissionId: string;
hashingSchemeVersion?: string;
minLedgerTime?: {
time: {
Empty: Record<string, never>;
};
};
}

export interface CantonOneStepEnablementRequest extends CantonPrepareCommandRequest {
receiverId: string;
}

export interface CantonTransferAcceptRequest extends CantonPrepareCommandRequest {
contractId: string;
}

export interface TransferAcknowledge {
contractId: string;
senderPartyId: string;
amount: number;
expiryEpoch: number;
updateId: string;
}
1 change: 1 addition & 0 deletions modules/sdk-coin-canton/src/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export { KeyPair } from './keyPair';
export { OneStepPreApprovalBuilder } from './oneStepPreApprovalBuilder';
export { Transaction } from './transaction/transaction';
export { TransferAcceptanceBuilder } from './transferAcceptanceBuilder';
export { TransferAcknowledgeBuilder } from './transferAcknowledgeBuilder';
export { TransactionBuilder } from './transactionBuilder';
export { TransactionBuilderFactory } from './transactionBuilderFactory';
export { WalletInitBuilder } from './walletInitBuilder';
Expand Down
14 changes: 13 additions & 1 deletion modules/sdk-coin-canton/src/lib/oneStepPreApprovalBuilder.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { TransactionType } from '@bitgo/sdk-core';
import { InvalidTransactionError, PublicKey, TransactionType } from '@bitgo/sdk-core';
import { BaseCoin as CoinConfig } from '@bitgo/statics';
import { CantonPrepareCommandResponse, CantonOneStepEnablementRequest } from './iface';
import { TransactionBuilder } from './transactionBuilder';
import { Transaction } from './transaction/transaction';
import utils from './utils';

export class OneStepPreApprovalBuilder extends TransactionBuilder {
private _commandId: string;
Expand All @@ -28,6 +29,17 @@ export class OneStepPreApprovalBuilder extends TransactionBuilder {
this.transaction.prepareCommand = transaction;
}

/** @inheritDoc */
addSignature(publicKey: PublicKey, signature: Buffer): void {
if (!this.transaction) {
throw new InvalidTransactionError('transaction is empty!');
}
this._signatures.push({ publicKey, signature });
const pubKeyBase64 = utils.getBase64FromHex(publicKey.pub);
this.transaction.signerFingerprint = utils.getAddressFromPublicKey(pubKeyBase64);
this.transaction.signatures = signature.toString('base64');
}

/**
* Sets the unique id for the 1-step enablement
* Also sets the _id of the transaction
Expand Down
97 changes: 90 additions & 7 deletions modules/sdk-coin-canton/src/lib/transaction/transaction.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
import { BaseKey, BaseTransaction, InvalidTransactionError, TransactionType } from '@bitgo/sdk-core';
import { BaseCoin as CoinConfig } from '@bitgo/statics';
import { CantonPrepareCommandResponse, PreparedTxnParsedInfo, TxData } from '../iface';
import {
CantonPrepareCommandResponse,
MultiHashSignature,
PartySignature,
PreparedTxnParsedInfo,
TransactionBroadcastData,
TxData,
} from '../iface';
import utils from '../utils';
import { DUMMY_HASH, HASHING_SCHEME_VERSION, SIGNATURE_ALGORITHM_SPEC, SIGNATURE_FORMAT } from '../constant';

export class Transaction extends BaseTransaction {
private _prepareCommand: CantonPrepareCommandResponse;
private _signerFingerprint: string;

constructor(coinConfig: Readonly<CoinConfig>) {
super(coinConfig);
Expand Down Expand Up @@ -37,23 +46,84 @@ export class Transaction extends BaseTransaction {
return false;
}

set signatures(signature: string) {
this._signatures.push(signature);
}

set signerFingerprint(fingerprint: string) {
this._signerFingerprint = fingerprint;
}

toBroadcastFormat(): string {
if (!this._type) {
throw new InvalidTransactionError('Transaction type is not set');
}
if (this._type === TransactionType.TransferAcknowledge) {
const minData: TransactionBroadcastData = {
txType: TransactionType[this._type],
submissionId: this.id,
};
return Buffer.from(JSON.stringify(minData)).toString('base64');
}
if (!this._prepareCommand) {
throw new InvalidTransactionError('Empty transaction data');
}
return Buffer.from(JSON.stringify(this._prepareCommand)).toString('base64');
const partySignatures: PartySignature[] = [];
const data: TransactionBroadcastData = {
prepareCommandResponse: this._prepareCommand,
txType: this._type ? TransactionType[this._type] : '',
preparedTransaction: '',
partySignatures: {
signatures: partySignatures,
},
deduplicationPeriod: {
Empty: {},
},
submissionId: this.id,
hashingSchemeVersion: HASHING_SCHEME_VERSION,
minLedgerTime: {
time: {
Empty: {},
},
},
};
const signatures: MultiHashSignature[] = [];
if (this._signatures.length > 0 && this._signerFingerprint) {
const signerPartyId = `${this._signerFingerprint.slice(0, 5)}::${this._signerFingerprint}`;
this.signature.map((signature) => {
const signatureObj: MultiHashSignature = {
format: SIGNATURE_FORMAT,
signature: signature,
signedBy: this._signerFingerprint,
signingAlgorithmSpec: SIGNATURE_ALGORITHM_SPEC,
};
signatures.push(signatureObj);
});
const partySignature = {
party: signerPartyId,
signatures: signatures,
};
data.partySignatures?.signatures.push(partySignature);
data.preparedTransaction = this._prepareCommand.preparedTransaction
? this._prepareCommand.preparedTransaction
: '';
}
return Buffer.from(JSON.stringify(data)).toString('base64');
}

toJson(): TxData {
if (!this._prepareCommand || !this._prepareCommand.preparedTransaction) {
throw new InvalidTransactionError('Empty transaction data');
}
const result: TxData = {
id: this.id,
type: this._type as TransactionType,
sender: '',
receiver: '',
};
if (this._type === TransactionType.TransferAcknowledge) {
return result;
}
if (!this._prepareCommand || !this._prepareCommand.preparedTransaction) {
throw new InvalidTransactionError('Empty transaction data');
}
// TODO: extract other required data (utxo used, request time, execute before etc)
let parsedInfo: PreparedTxnParsedInfo;
try {
Expand All @@ -67,6 +137,9 @@ export class Transaction extends BaseTransaction {
}

get signablePayload(): Buffer {
if (this._type === TransactionType.TransferAcknowledge) {
return Buffer.from(DUMMY_HASH, 'base64');
}
if (!this._prepareCommand) {
throw new InvalidTransactionError('Empty transaction data');
}
Expand All @@ -75,8 +148,18 @@ export class Transaction extends BaseTransaction {

fromRawTransaction(rawTx: string): void {
try {
const decoded: CantonPrepareCommandResponse = JSON.parse(Buffer.from(rawTx, 'base64').toString('utf8'));
this.prepareCommand = decoded;
const decoded: TransactionBroadcastData = JSON.parse(Buffer.from(rawTx, 'base64').toString('utf8'));
this.id = decoded.submissionId;
this.transactionType = TransactionType[decoded.txType];
if (this.transactionType !== TransactionType.TransferAcknowledge) {
if (decoded.prepareCommandResponse) {
this.prepareCommand = decoded.prepareCommandResponse;
}
if (decoded.partySignatures && decoded.partySignatures.signatures.length > 0) {
this.signerFingerprint = decoded.partySignatures.signatures[0].party.split('::')[1];
this.signatures = decoded.partySignatures.signatures[0].signatures[0].signature;
}
}
} catch (e) {
throw new InvalidTransactionError('Unable to parse raw transaction data');
}
Expand Down
6 changes: 2 additions & 4 deletions modules/sdk-coin-canton/src/lib/transactionBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import utils from './utils';

export abstract class TransactionBuilder extends BaseTransactionBuilder {
protected _transaction: Transaction;
private _signatures: Signature[] = [];
protected _signatures: Signature[] = [];

initBuilder(tx: Transaction): void {
this._transaction = tx;
Expand All @@ -40,9 +40,7 @@ export abstract class TransactionBuilder extends BaseTransactionBuilder {
}

/** @inheritDoc */
addSignature(publicKey: BasePublicKey, signature: Buffer): void {
this._signatures.push({ publicKey, signature });
}
abstract addSignature(publicKey: BasePublicKey, signature: Buffer): void;

/** @inheritdoc */
protected fromImplementation(rawTransaction: string): Transaction {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
} from '@bitgo/sdk-core';
import { BaseCoin as CoinConfig } from '@bitgo/statics';
import { TransferAcceptanceBuilder } from './transferAcceptanceBuilder';
import { TransferAcknowledgeBuilder } from './transferAcknowledgeBuilder';
import { TransactionBuilder } from './transactionBuilder';
import { TransferBuilder } from './transferBuilder';
import { Transaction } from './transaction/transaction';
Expand All @@ -32,18 +33,24 @@ export class TransactionBuilderFactory extends BaseTransactionBuilderFactory {
case TransactionType.TransferAccept: {
return this.getTransferAcceptanceBuilder(tx);
}
case TransactionType.TransferAcknowledge: {
return this.getTransferAcknowledgeBuilder(tx);
}
default: {
throw new InvalidTransactionError('unsupported transaction');
}
}
}
}

/** @inheritdoc */
getTransferAcceptanceBuilder(tx?: Transaction): TransferAcceptanceBuilder {
return TransactionBuilderFactory.initializeBuilder(tx, new TransferAcceptanceBuilder(this._coinConfig));
}

getTransferAcknowledgeBuilder(tx?: Transaction): TransferAcknowledgeBuilder {
return TransactionBuilderFactory.initializeBuilder(tx, new TransferAcknowledgeBuilder(this._coinConfig));
}

/** @inheritdoc */
getTransferBuilder(tx?: Transaction): TransferBuilder {
return TransactionBuilderFactory.initializeBuilder(tx, new TransferBuilder(this._coinConfig));
Expand Down
14 changes: 13 additions & 1 deletion modules/sdk-coin-canton/src/lib/transferAcceptanceBuilder.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { TransactionType } from '@bitgo/sdk-core';
import { InvalidTransactionError, PublicKey, TransactionType } from '@bitgo/sdk-core';
import { BaseCoin as CoinConfig } from '@bitgo/statics';
import { CantonPrepareCommandResponse, CantonTransferAcceptRequest } from './iface';
import { TransactionBuilder } from './transactionBuilder';
import { Transaction } from './transaction/transaction';
import utils from './utils';

export class TransferAcceptanceBuilder extends TransactionBuilder {
private _commandId: string;
Expand All @@ -29,6 +30,17 @@ export class TransferAcceptanceBuilder extends TransactionBuilder {
this.transaction.prepareCommand = transaction;
}

/** @inheritDoc */
addSignature(publicKey: PublicKey, signature: Buffer): void {
if (!this.transaction) {
throw new InvalidTransactionError('transaction is empty!');
}
this._signatures.push({ publicKey, signature });
const pubKeyBase64 = utils.getBase64FromHex(publicKey.pub);
this.transaction.signerFingerprint = utils.getAddressFromPublicKey(pubKeyBase64);
this.transaction.signatures = signature.toString('base64');
}

/**
* Sets the unique id for the transfer acceptance
* Also sets the _id of the transaction
Expand Down
Loading