Skip to content

Commit

Permalink
Extract Relayed Signatures (#94)
Browse files Browse the repository at this point in the history
[This transaction](https://testnet.nearblocks.io/txns/G1f1HVUxDBWXAEimgNWobQ9yCx1EgA2tzYHJBFUfo3dj) was relayed by mintbase.testnet on behalf of neareth-dev.testnet. The signature is nested deep inside the transaction instead of on the outer successValue.

We introduce a method for digging out the signature from the receipt that is compatible with both scenarios.

Still need to verify that signatures are indeed valid.
  • Loading branch information
bh2smith authored Aug 9, 2024
1 parent 55a4dba commit 774abe2
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 10 deletions.
54 changes: 46 additions & 8 deletions src/utils/signature.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,16 +71,54 @@ export function signatureFromOutcome(
// The Partial object is intended to make up for the
// difference between all the different near-api versions and wallet-selector bullshit
// the field `final_execution_status` is in one, but not the other and we don't use it anyway.
outcome: FinalExecutionOutcome | Partial<FinalExecutionOutcome>
outcome:
| FinalExecutionOutcome
| Omit<FinalExecutionOutcome, "final_execution_status">
): Signature {
// TODO: Find example outcome when status is not of this casted type.
const b64Sig = (outcome.status as FinalExecutionStatus).SuccessValue;
if (b64Sig) {
const decodedValue = Buffer.from(b64Sig, "base64").toString("utf-8");
const signature = JSON.parse(decodedValue);
const txHash = outcome.transaction_outcome?.id;
// TODO - find a scenario when outcome.status is `FinalExecutionStatusBasic`!
let b64Sig = (outcome.status as FinalExecutionStatus).SuccessValue;
if (!b64Sig) {
// This scenario occurs when sign call is relayed (i.e. executed by someone else).
// E.g. https://testnet.nearblocks.io/txns/G1f1HVUxDBWXAEimgNWobQ9yCx1EgA2tzYHJBFUfo3dj
// We have to dig into `receipts_outcome` and extract the signature from within.
// We want the second occurence of the signature because
// the first is nested inside `{ Ok: MPCSignature }`)
b64Sig = outcome.receipts_outcome
// Map to get SuccessValues: The Signature will appear twice.
.map(
(receipt) =>
(receipt.outcome.status as FinalExecutionStatus).SuccessValue
)
// Reverse the to "find" the last non-empty value!
.reverse()
.find((value) => value && value.trim().length > 0);
}
if (!b64Sig) {
throw new Error(`No detectable signature found in transaction ${txHash}`);
}
if (b64Sig === "eyJFcnIiOiJGYWlsZWQifQ==") {
// {"Err": "Failed"}
throw new Error(`Signature Request Failed in ${txHash}`);
}
const decodedValue = Buffer.from(b64Sig, "base64").toString("utf-8");
const signature = JSON.parse(decodedValue);
if (isMPCSignature(signature)) {
return transformSignature(signature);
} else {
throw new Error(`No detectable signature found in transaction ${txHash}`);
}
throw new Error(
`No detectable signature found in transaction ${outcome.transaction_outcome?.id}`
}

// type guard for MPCSignature object.
function isMPCSignature(obj: unknown): obj is MPCSignature {
return (
typeof obj === "object" &&
obj !== null &&
typeof (obj as MPCSignature).big_r === "object" &&
typeof (obj as MPCSignature).big_r.affine_point === "string" &&
typeof (obj as MPCSignature).s === "object" &&
typeof (obj as MPCSignature).s.scalar === "string" &&
typeof (obj as MPCSignature).recovery_id === "number"
);
}
44 changes: 42 additions & 2 deletions tests/unit/utils.signature.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ describe("utility: get Signature", () => {
const url: string = "https://archival-rpc.testnet.near.org";
// const accountId = "neareth-dev.testnet";
const successHash = "GeqmwWmWxddzh2yCEhugbJkhzsJMhCuFyQZa61w5dk7N";
const relayedSuccessHash = "G1f1HVUxDBWXAEimgNWobQ9yCx1EgA2tzYHJBFUfo3dj";
const failedHash = "6yRm5FjHn9raRYPoHH6wimizhT53PnPnuvkpecyQDqLY";
const nonExistantTxHash = "7yRm5FjHn9raRYPoHH6wimizhT53PnPnuvkpecyQDqLY";
const nonSignatureRequestHash =
"4pNDN238dgEjj5eNaAF4qzoztF4TmrN82hwJs2zTwuqe";

it("successful: signatureFromTxHash", async () => {
const sig = await signatureFromTxHash(url, successHash);
Expand All @@ -17,11 +21,33 @@ describe("utility: get Signature", () => {
s: "0x5194CCE1D9F239C28C7765453873A07F35850A485DFE285551FB62C899B61170",
yParity: 1,
});

const relayedSig = await signatureFromTxHash(url, relayedSuccessHash);
expect(relayedSig).toEqual({
r: "0x593873A56AB98F91C60C23DCA370835CA05254A0305F2753A1CFC3CEB4C46F86",
s: "0x783D9887FB4AA9B07E672D7FA88587FB45E7FDC066F7ECA0774E6FE36806404F",
yParity: 1,
});
});

it("signatureFromTxHash fails with no signature", async () => {
it("signatureFromTxHash fails Error", async () => {
await expect(signatureFromTxHash(url, failedHash)).rejects.toThrow(
`No detectable signature found in transaction ${failedHash}`
`Signature Request Failed in ${failedHash}`
);
});

it("signatureFromTxHash fails with no signature", async () => {
await expect(
signatureFromTxHash(url, nonSignatureRequestHash)
).rejects.toThrow(
`No detectable signature found in transaction ${nonSignatureRequestHash}`
);
});

// This one takes too long.
it.skip("signatureFromTxHash fails with server error", async () => {
await expect(signatureFromTxHash(url, nonExistantTxHash)).rejects.toThrow(
"JSON-RPC error: Server error"
);
});

Expand Down Expand Up @@ -57,6 +83,20 @@ describe("utility: get Signature", () => {
SuccessValue:
"eyJiaWdfciI6eyJhZmZpbmVfcG9pbnQiOiIwMkVBRDJCMUYwN0NDNDk4REIyNTU2MzE0QTZGNzdERkUzRUUzRDE0NTNCNkQ3OTJBNzcwOTE5MjRFNTFENEMyNDcifSwicyI6eyJzY2FsYXIiOiIxQTlGNjBDMkNDMjM5OEE1MDk3N0Q0Q0E5M0M0MDE2OEU4RjJDRTdBOUM5MEUzNzQ1MjJERjNDNzZDRjU0RjJFIn0sInJlY292ZXJ5X2lkIjoxfQ==",
},
// irrelevant fields
receipts_outcome: [],
transaction: null,
transaction_outcome: {
id: "",
outcome: {
logs: [],
receipt_ids: [],
gas_burnt: 0,
tokens_burnt: "",
executor_id: "string",
status: {},
},
},
};
expect(signatureFromOutcome(outcome)).toEqual({
r: "0xEAD2B1F07CC498DB2556314A6F77DFE3EE3D1453B6D792A77091924E51D4C247",
Expand Down

0 comments on commit 774abe2

Please sign in to comment.