Skip to content

Commit fd77718

Browse files
authored
Merge pull request #5861 from BitGo/WIN-4983_icp_payload
feat(sdk-coin-icp): reduce the payload to 1
2 parents 0153243 + c6905ce commit fd77718

File tree

8 files changed

+66
-176
lines changed

8 files changed

+66
-176
lines changed

modules/sdk-coin-icp/src/lib/iface.ts

Lines changed: 3 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,12 @@ import {
33
TransactionType as BitGoTransactionType,
44
} from '@bitgo/sdk-core';
55

6-
export const REQUEST_STATUS = 'request_status';
76
export const MAX_INGRESS_TTL = 5 * 60 * 1000_000_000; // 5 minutes in nanoseconds
87
export const PERMITTED_DRIFT = 60 * 1000_000_000; // 60 seconds in nanoseconds
98
export const LEDGER_CANISTER_ID = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 2, 1, 1]); // Uint8Array value for "00000000000000020101" and the string value is "ryjl3-tyaaa-aaaaa-aaaba-cai"
109

1110
export enum RequestType {
1211
CALL = 'call',
13-
READ_STATE = 'read_state',
1412
}
1513

1614
export enum SignatureType {
@@ -127,32 +125,14 @@ export interface Signatures {
127125
hex_bytes: string;
128126
}
129127

128+
//Todo: remove this interface when we have a better way to handle the transaction
130129
export interface CborUnsignedTransaction {
131-
updates: [string, HttpCanisterUpdate][];
130+
updates: [HttpCanisterUpdate][];
132131
ingress_expiries: bigint[];
133132
}
134133

135-
export interface ReadState {
136-
sender: Uint8Array;
137-
paths: Array<[Buffer, Buffer]>;
138-
ingress_expiry: bigint;
139-
}
140-
141134
export interface UpdateEnvelope {
142-
content: {
143-
request_type: RequestType;
144-
canister_id: Uint8Array;
145-
method_name: MethodName;
146-
arg: Uint8Array;
147-
sender: Uint8Array;
148-
ingress_expiry: bigint;
149-
};
150-
sender_pubkey: Uint8Array;
151-
sender_sig: Uint8Array;
152-
}
153-
154-
export interface ReadStateEnvelope {
155-
content: ReadState & {
135+
content: HttpCanisterUpdate & {
156136
request_type: RequestType;
157137
};
158138
sender_pubkey: Uint8Array;
@@ -161,7 +141,6 @@ export interface ReadStateEnvelope {
161141

162142
export interface RequestEnvelope {
163143
update: UpdateEnvelope;
164-
read_state: ReadStateEnvelope;
165144
}
166145

167146
/**
@@ -188,11 +167,6 @@ export interface NetworkIdentifier {
188167
network: string;
189168
}
190169

191-
export interface SignedTransactionRequest {
192-
network_identifier: NetworkIdentifier;
193-
signed_transaction: string;
194-
}
195-
196170
export interface RawTransaction {
197171
serializedTxHex: string;
198172
publicKey: string;

modules/sdk-coin-icp/src/lib/signedTransactionBuilder.ts

Lines changed: 30 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import {
33
RequestType,
44
Signatures,
55
UpdateEnvelope,
6-
ReadStateEnvelope,
76
RequestEnvelope,
7+
HttpCanisterUpdate,
88
} from './iface';
99
import utils from './utils';
1010
import assert from 'assert';
@@ -30,54 +30,48 @@ export class SignedTransactionBuilder {
3030
const unsignedTransaction = utils.cborDecode(
3131
utils.blobFromHex(combineRequest.unsigned_transaction)
3232
) as CborUnsignedTransaction;
33-
assert(combineRequest.signatures.length === unsignedTransaction.ingress_expiries.length * 2);
33+
// at present we expect only one request type and one signature
34+
assert(combineRequest.signatures.length === 1);
3435
assert(unsignedTransaction.updates.length === 1);
3536
const envelopes = this.getEnvelopes(unsignedTransaction, signatureMap);
36-
const envelopRequests = { requests: envelopes };
37-
const signedTransaction = utils.cborEncode(envelopRequests);
37+
const requestEnvelopeArray = envelopes[0] as [RequestEnvelope[]];
38+
const requestEnvelopes = requestEnvelopeArray[0] as RequestEnvelope[];
39+
assert(requestEnvelopes.length === 1);
40+
const requestEnvelope = requestEnvelopes[0] as RequestEnvelope;
41+
const signedTransaction = utils.cborEncode(requestEnvelope.update as UpdateEnvelope);
3842
return signedTransaction;
3943
}
4044

4145
getEnvelopes(
4246
unsignedTransaction: CborUnsignedTransaction,
4347
signatureMap: Map<string, Signatures>
44-
): [string, RequestEnvelope[]][] {
45-
const envelopes: [string, RequestEnvelope[]][] = [];
46-
for (const [reqType, update] of unsignedTransaction.updates) {
48+
): [RequestEnvelope[]][] {
49+
const envelopes: [RequestEnvelope[]][] = [];
50+
for (const [, update] of unsignedTransaction.updates as unknown as [string, HttpCanisterUpdate][]) {
4751
const requestEnvelopes: RequestEnvelope[] = [];
48-
for (const ingressExpiry of unsignedTransaction.ingress_expiries) {
49-
update.ingress_expiry = ingressExpiry;
50-
51-
const readState = utils.makeReadStateFromUpdate(update);
52-
const transactionSignature = utils.getTransactionSignature(signatureMap, update);
53-
if (!transactionSignature) {
54-
throw new Error('Transaction signature is invalid');
55-
}
52+
if (unsignedTransaction.ingress_expiries.length != 1) {
53+
throw new Error('ingress expiry can have only one entry');
54+
}
55+
const ingressExpiry = unsignedTransaction.ingress_expiries[0];
56+
update.ingress_expiry = ingressExpiry;
5657

57-
const readStateSignature = utils.getReadStateSignature(signatureMap, readState);
58-
if (!readStateSignature) {
59-
throw new Error('read state signature is invalid');
60-
}
58+
const transactionSignature = utils.getTransactionSignature(signatureMap, update);
59+
if (!transactionSignature) {
60+
throw new Error('Transaction signature is invalid');
61+
}
6162

62-
const pk_der = utils.getPublicKeyInDERFormat(transactionSignature.public_key.hex_bytes);
63-
const updateEnvelope: UpdateEnvelope = {
64-
content: { request_type: RequestType.CALL, ...update },
65-
sender_pubkey: pk_der,
66-
sender_sig: utils.blobFromHex(transactionSignature.hex_bytes),
67-
};
63+
const pk_der = utils.getPublicKeyInDERFormat(transactionSignature.public_key.hex_bytes);
64+
const updateEnvelope: UpdateEnvelope = {
65+
content: { request_type: RequestType.CALL, ...update },
66+
sender_pubkey: pk_der,
67+
sender_sig: utils.blobFromHex(transactionSignature.hex_bytes),
68+
};
6869

69-
const readStateEnvelope: ReadStateEnvelope = {
70-
content: { request_type: RequestType.READ_STATE, ...readState },
71-
sender_pubkey: pk_der,
72-
sender_sig: utils.blobFromHex(readStateSignature.hex_bytes),
73-
};
70+
requestEnvelopes.push({
71+
update: updateEnvelope,
72+
});
7473

75-
requestEnvelopes.push({
76-
update: updateEnvelope,
77-
read_state: readStateEnvelope,
78-
});
79-
}
80-
envelopes.push([reqType, requestEnvelopes]);
74+
envelopes.push([requestEnvelopes]);
8175
}
8276
return envelopes;
8377
}

modules/sdk-coin-icp/src/lib/transaction.ts

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,11 @@ import {
1515
Signatures,
1616
TxData,
1717
IcpTransactionExplanation,
18-
SignedTransactionRequest,
19-
Network,
2018
CborUnsignedTransaction,
2119
HttpCanisterUpdate,
2220
ParsedTransaction,
2321
IcpOperation,
22+
UpdateEnvelope,
2423
IcpAccount,
2524
MAX_INGRESS_TTL,
2625
PERMITTED_DRIFT,
@@ -204,22 +203,15 @@ export class Transaction extends BaseTransaction {
204203
}
205204

206205
serialize(): string {
207-
const transaction: SignedTransactionRequest = {
208-
signed_transaction: this._signedTransaction,
209-
network_identifier: {
210-
blockchain: this._coinConfig.fullName,
211-
network: Network.ID,
212-
},
213-
};
214-
return JSON.stringify(transaction);
206+
return this._signedTransaction;
215207
}
216208

217209
async parseUnsignedTransaction(rawTransaction: string): Promise<ParsedTransaction> {
218210
const unsignedTransaction = this._utils.cborDecode(
219211
this._utils.blobFromHex(rawTransaction)
220212
) as CborUnsignedTransaction;
221213
const update = unsignedTransaction.updates[0];
222-
const httpCanisterUpdate = update[1] as HttpCanisterUpdate;
214+
const httpCanisterUpdate = (update as unknown as [string, HttpCanisterUpdate])[1];
223215
return await this.getParsedTransactionFromUpdate(httpCanisterUpdate, false);
224216
}
225217

@@ -288,10 +280,7 @@ export class Transaction extends BaseTransaction {
288280

289281
async parseSignedTransaction(rawTransaction: string): Promise<ParsedTransaction> {
290282
const signedTransaction = this._utils.cborDecode(this._utils.blobFromHex(rawTransaction));
291-
const signedTransactionTyped = signedTransaction as { requests: any[] };
292-
const envelopes = signedTransactionTyped.requests[0][1];
293-
const updates = envelopes.map((envelope) => envelope.update);
294-
const httpCanisterUpdate = updates[0].content as HttpCanisterUpdate;
283+
const httpCanisterUpdate = (signedTransaction as UpdateEnvelope).content as HttpCanisterUpdate;
295284
return await this.getParsedTransactionFromUpdate(httpCanisterUpdate, true);
296285
}
297286

modules/sdk-coin-icp/src/lib/transactionBuilder.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import BigNumber from 'bignumber.js';
33
import { BaseTransactionBuilder, BuildTransactionError, BaseAddress, SigningError, BaseKey } from '@bitgo/sdk-core';
44
import { Transaction } from './transaction';
55
import utils from './utils';
6-
import { IcpTransactionData, Signatures } from './iface';
6+
import { IcpTransaction, IcpTransactionData, PayloadsData, Signatures } from './iface';
77
import { SignedTransactionBuilder } from './signedTransactionBuilder';
88

99
export abstract class TransactionBuilder extends BaseTransactionBuilder {
@@ -23,8 +23,12 @@ export abstract class TransactionBuilder extends BaseTransactionBuilder {
2323
return this._transaction.signaturePayload;
2424
}
2525

26-
public unsignedTransaction(): string {
27-
return this._transaction.payloadsData.unsigned_transaction;
26+
public payloadData(): PayloadsData {
27+
return this._transaction.payloadsData;
28+
}
29+
30+
public icpTransaction(): IcpTransaction {
31+
return this._transaction.icpTransaction;
2832
}
2933

3034
/**

modules/sdk-coin-icp/src/lib/unsignedTransactionBuilder.ts

Lines changed: 17 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -56,33 +56,25 @@ export class UnsignedTransactionBuilder {
5656
accountAddress: string,
5757
update: HttpCanisterUpdate
5858
): SigningPayload[] {
59-
for (const ingressExpiry of ingressExpiries) {
60-
const clonedUpdate: HttpCanisterUpdate = {
61-
canister_id: Buffer.from(update.canister_id),
62-
method_name: update.method_name,
63-
arg: update.arg,
64-
sender: update.sender,
65-
ingress_expiry: ingressExpiry,
66-
};
67-
68-
const representationIndependentHash = utils.HttpCanisterUpdateRepresentationIndependentHash(clonedUpdate);
69-
const transactionPayload: SigningPayload = {
70-
hex_bytes: utils.blobToHex(utils.makeSignatureData(representationIndependentHash)),
71-
account_identifier: { address: accountAddress },
72-
signature_type: SignatureType.ECDSA,
73-
};
74-
payloads.push(transactionPayload);
75-
76-
const readState = utils.makeReadStateFromUpdate(clonedUpdate);
77-
const readStateMessageId = utils.HttpReadStateRepresentationIndependentHash(readState);
78-
const readStatePayload: SigningPayload = {
79-
hex_bytes: utils.blobToHex(utils.makeSignatureData(readStateMessageId)),
80-
account_identifier: { address: accountAddress },
81-
signature_type: SignatureType.ECDSA,
82-
};
83-
payloads.push(readStatePayload);
59+
if (ingressExpiries.length != 1) {
60+
throw new Error('ingress expiry can have only one entry');
8461
}
62+
const ingressExpiry = ingressExpiries[0];
63+
const clonedUpdate: HttpCanisterUpdate = {
64+
canister_id: Buffer.from(update.canister_id),
65+
method_name: update.method_name,
66+
arg: update.arg,
67+
sender: update.sender,
68+
ingress_expiry: ingressExpiry,
69+
};
8570

71+
const representationIndependentHash = utils.HttpCanisterUpdateRepresentationIndependentHash(clonedUpdate);
72+
const transactionPayload: SigningPayload = {
73+
hex_bytes: utils.blobToHex(utils.makeSignatureData(representationIndependentHash)),
74+
account_identifier: { address: accountAddress },
75+
signature_type: SignatureType.ECDSA,
76+
};
77+
payloads.push(transactionPayload);
8678
return payloads;
8779
}
8880

modules/sdk-coin-icp/src/lib/utils.ts

Lines changed: 0 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import crc32 from 'crc-32';
1313
import {
1414
HttpCanisterUpdate,
1515
IcpTransactionData,
16-
ReadState,
1716
RequestType,
1817
Signatures,
1918
IcpMetadata,
@@ -29,8 +28,6 @@ import { secp256k1 } from '@noble/curves/secp256k1';
2928
import protobuf from 'protobufjs';
3029
import { protoDefinition } from './protoDefinition';
3130

32-
export const REQUEST_STATUS = 'request_status';
33-
3431
export class Utils implements BaseUtils {
3532
/** @inheritdoc */
3633
isValidSignature(signature: string): boolean {
@@ -556,35 +553,6 @@ export class Utils implements BaseUtils {
556553
return Buffer.concat([this.getDomainICRequest(), messageId]);
557554
}
558555

559-
/**
560-
* Generates a read state object from an HTTP canister update.
561-
*
562-
* @param {HttpCanisterUpdate} update - The HTTP canister update object.
563-
* @returns {ReadState} The read state object containing the sender, paths, and ingress expiry.
564-
*/
565-
makeReadStateFromUpdate(update: HttpCanisterUpdate): ReadState {
566-
return {
567-
sender: update.sender,
568-
paths: [[Buffer.from(REQUEST_STATUS), this.generateHttpCanisterUpdateId(update)]],
569-
ingress_expiry: update.ingress_expiry,
570-
};
571-
}
572-
573-
/**
574-
* Generates a representation-independent hash for an HTTP read state object.
575-
*
576-
* @param {ReadState} readState - The HTTP read state object.
577-
* @returns {Buffer} - The hash of the read state object.
578-
*/
579-
HttpReadStateRepresentationIndependentHash(readState: ReadState): Buffer {
580-
return this.hashOfMap({
581-
request_type: RequestType.READ_STATE,
582-
ingress_expiry: readState.ingress_expiry,
583-
paths: readState.paths,
584-
sender: readState.sender,
585-
});
586-
}
587-
588556
/**
589557
* Extracts the recipient information from the provided ICP transaction data.
590558
*
@@ -602,12 +570,6 @@ export class Utils implements BaseUtils {
602570
return signatureMap.get(this.blobToHex(this.makeSignatureData(this.generateHttpCanisterUpdateId(update))));
603571
}
604572

605-
getReadStateSignature(signatureMap: Map<string, Signatures>, readState: ReadState): Signatures | undefined {
606-
return signatureMap.get(
607-
this.blobToHex(this.makeSignatureData(this.HttpReadStateRepresentationIndependentHash(readState)))
608-
);
609-
}
610-
611573
getMetaData(
612574
memo: number | BigInt,
613575
timestamp: number | bigint | undefined

modules/sdk-coin-icp/test/resources/icp.ts

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -179,11 +179,6 @@ export const payloadsData = {
179179
hex_bytes: '0a69632d726571756573742dd9527902e38bc015d4f5521e680b29d1ebc5b4fe24dd5515e2abe99097f0b1',
180180
signature_type: 'ecdsa',
181181
},
182-
{
183-
account_identifier: { address: '0af815da8259ba8bb3d34fbfb2ac730f07a1adc81438d40d667d91b408b25f2f' },
184-
hex_bytes: '0a69632d726571756573742adff4e1c4b8b725bd4accbaf80cd2b180b322bfc450ec00d14aa315c9a4e324',
185-
signature_type: 'ecdsa',
186-
},
187182
],
188183
unsigned_transaction:
189184
'b90002677570646174657381826b5452414e53414354494f4eb900056b63616e69737465725f69644a000000000000000201016b6d6574686f645f6e616d656773656e645f70626361726758400a0308d20912040a02080a1a0308904e2a220a20c3d30f404955975adaba89f2e1ebc75c1f44a6a204578afce8f3780d64fe252e3a0a088084dcb3abb69694186673656e646572581dd5fc1dc4d74d4aa35d81cf345533d20548113412d32fffdcece2f68a026e696e67726573735f6578706972791b000000000000000070696e67726573735f6578706972696573811b182859ea97946200',
@@ -205,25 +200,10 @@ export const signatures = [
205200
hex_bytes:
206201
'11d903fe529788a2202a261568066371e2022ddb339379368e19567473b3c2d75771fcd97b9adef613726109499ff3b7248e127da421b34b4959aa9e9fb92dcc',
207202
},
208-
{
209-
signing_payload: {
210-
account_identifier: { address: '0af815da8259ba8bb3d34fbfb2ac730f07a1adc81438d40d667d91b408b25f2f' },
211-
hex_bytes: '0a69632d726571756573742adff4e1c4b8b725bd4accbaf80cd2b180b322bfc450ec00d14aa315c9a4e324',
212-
signature_type: 'ecdsa',
213-
},
214-
signature_type: 'ecdsa',
215-
public_key: {
216-
hex_bytes:
217-
'042ab77b959e28c4fa47fa8fb9e57cec3d66df5684d076ac2e4c5f28fd69a23dd31a59f908c8add51eab3530b4ac5d015166eaf2198c52fa9a8df7cfaeb8fdb7d4',
218-
curve_type: 'secp256k1',
219-
},
220-
hex_bytes:
221-
'b50df90e4c94e45d4bd7117d98380d104f19cf7053814887acfe4d6ad24b08f8735473b00e5740beef2e675d98764dc22ab44c653ecf65281cc46601d2802eb5',
222-
},
223203
];
224204

225205
export const signedTransaction =
226-
'b9000168726571756573747381826b5452414e53414354494f4e81b9000266757064617465b9000367636f6e74656e74b900066c726571756573745f747970656463616c6c6b63616e69737465725f69644a000000000000000201016b6d6574686f645f6e616d656773656e645f70626361726758400a0308d20912040a02080a1a0308904e2a220a20c3d30f404955975adaba89f2e1ebc75c1f44a6a204578afce8f3780d64fe252e3a0a088084dcb3abb69694186673656e646572581dd5fc1dc4d74d4aa35d81cf345533d20548113412d32fffdcece2f68a026e696e67726573735f6578706972791b182859ea979462006d73656e6465725f7075626b6579d84058583056301006072a8648ce3d020106052b8104000a034200042ab77b959e28c4fa47fa8fb9e57cec3d66df5684d076ac2e4c5f28fd69a23dd31a59f908c8add51eab3530b4ac5d015166eaf2198c52fa9a8df7cfaeb8fdb7d46a73656e6465725f736967584011d903fe529788a2202a261568066371e2022ddb339379368e19567473b3c2d75771fcd97b9adef613726109499ff3b7248e127da421b34b4959aa9e9fb92dcc6a726561645f7374617465b9000367636f6e74656e74b900046c726571756573745f747970656a726561645f73746174656673656e646572581dd5fc1dc4d74d4aa35d81cf345533d20548113412d32fffdcece2f68a0265706174687381824e726571756573745f73746174757358202dd9527902e38bc015d4f5521e680b29d1ebc5b4fe24dd5515e2abe99097f0b16e696e67726573735f6578706972791b182859ea979462006d73656e6465725f7075626b6579d84058583056301006072a8648ce3d020106052b8104000a034200042ab77b959e28c4fa47fa8fb9e57cec3d66df5684d076ac2e4c5f28fd69a23dd31a59f908c8add51eab3530b4ac5d015166eaf2198c52fa9a8df7cfaeb8fdb7d46a73656e6465725f7369675840b50df90e4c94e45d4bd7117d98380d104f19cf7053814887acfe4d6ad24b08f8735473b00e5740beef2e675d98764dc22ab44c653ecf65281cc46601d2802eb5';
206+
'b9000367636f6e74656e74b900066c726571756573745f747970656463616c6c6b63616e69737465725f69644a000000000000000201016b6d6574686f645f6e616d656773656e645f70626361726758400a0308d20912040a02080a1a0308904e2a220a20c3d30f404955975adaba89f2e1ebc75c1f44a6a204578afce8f3780d64fe252e3a0a088084dcb3abb69694186673656e646572581dd5fc1dc4d74d4aa35d81cf345533d20548113412d32fffdcece2f68a026e696e67726573735f6578706972791b182859ea979462006d73656e6465725f7075626b6579d84058583056301006072a8648ce3d020106052b8104000a034200042ab77b959e28c4fa47fa8fb9e57cec3d66df5684d076ac2e4c5f28fd69a23dd31a59f908c8add51eab3530b4ac5d015166eaf2198c52fa9a8df7cfaeb8fdb7d46a73656e6465725f736967584011d903fe529788a2202a261568066371e2022ddb339379368e19567473b3c2d75771fcd97b9adef613726109499ff3b7248e127da421b34b4959aa9e9fb92dcc';
227207

228208
export const ParsedUnsignedTransaction = {
229209
operations: [

0 commit comments

Comments
 (0)