Skip to content

Commit

Permalink
feat: implement erc4361 msg in lib
Browse files Browse the repository at this point in the history
  • Loading branch information
Z4karia committed Oct 10, 2024
1 parent 5ec9fb3 commit b17e8cc
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 5 deletions.
28 changes: 28 additions & 0 deletions src/AcreBtcNew.ts
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,7 @@ export default class AcreBtcNew {
s,
};
}

cleanHexPrefix(hexString: string): string {
let cleanedHex = hexString.startsWith("0x") ? hexString.slice(2) : hexString;
if (cleanedHex.length % 2 !== 0) {
Expand Down Expand Up @@ -401,6 +402,33 @@ export default class AcreBtcNew {
};
}

/**
* Signs a ERC4361 hex-formatted message with the private key at
* the provided derivation path according to the Bitcoin Signature format
* and returns v, r, s.
*/
async signERC4361Message({ path, messageHex }: { path: string; messageHex: string }): Promise<{
v: number;
r: string;
s: string;
}> {
const pathElements: number[] = pathStringToArray(path);
const message = Buffer.from(messageHex, "hex");
const sig = await this.client.signERC4361Message(message, pathElements);
console.log("sig", sig);
const buf = Buffer.from(sig, "base64");

const v = buf.readUInt8() - 27 - 4;
const r = buf.slice(1, 33).toString("hex");
const s = buf.slice(33, 65).toString("hex");

return {
v,
r,
s,
};
}

/**
* Calculates an output script along with public key and possible redeemScript
* from a path and accountType. The accountPath must be a prefix of path.
Expand Down
29 changes: 28 additions & 1 deletion src/newops/appClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ enum BitcoinIns {
SIGN_PSBT = 0x04,
GET_MASTER_FINGERPRINT = 0x05,
SIGN_MESSAGE = 0x10,
SIGN_WITHDRAW = 0x11
SIGN_WITHDRAW = 0x11,
SIGN_ERC4361_MESSAGE = 0x12
}

enum FrameworkIns {
Expand Down Expand Up @@ -247,4 +248,30 @@ export class AppClient {

return response.toString("base64")
}

async signERC4361Message(message: Buffer, pathElements: number[]): Promise<string> {
if (pathElements.length > 6) {
throw new Error("Path too long. At most 6 levels allowed.");
}

const clientInterpreter = new ClientCommandInterpreter(() => {});

// prepare ClientCommandInterpreter
const nChunks = Math.ceil(message.length / 64);
const chunks: Buffer[] = [];
for (let i = 0; i < nChunks; i++) {
chunks.push(message.subarray(64 * i, 64 * i + 64));
}

clientInterpreter.addKnownList(chunks);
const chunksRoot = new Merkle(chunks.map(m => hashLeaf(m))).getRoot();

const response = await this.makeRequest(
BitcoinIns.SIGN_ERC4361_MESSAGE,
Buffer.concat([pathElementsToBuffer(pathElements), createVarint(message.length), chunksRoot]),
clientInterpreter,
);

return response.toString("base64");
}
}
20 changes: 16 additions & 4 deletions tests/newops/AcreBtcNew.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import { openTransportReplayer, RecordStore } from "@ledgerhq/hw-transport-mocker";
import { TransportReplayer } from "@ledgerhq/hw-transport-mocker/lib/openTransportReplayer";
import SpeculosTransport from "../speculosTransport";
import ecc from "tiny-secp256k1";
import { getXpubComponents, pathArrayToString } from "../../src/bip32";
import AcreBtcNew from "../../src/AcreBtcNew";
Expand Down Expand Up @@ -57,9 +58,10 @@ test("testSignMessage", async () => {
await testSignMessageReplayer("m/44'/0'/0'");
});

test("signWithdrawal", async () => {
await testSignWithdrawalReplayer();
});

test("Sign ERC-4361 message", async () => {
await testSignERC4361Speculos();
}, 60 * 10 * 1000); // 10-minute timeout (60 seconds * 10 minutes * 1000 milliseconds)

function testPaths(type: StandardPurpose): { ins: string[]; out?: string } {
const basePath = `m/${type}/1'/0'/`;
Expand Down Expand Up @@ -228,6 +230,16 @@ async function testSignWithdrawalReplayer() {
});
}

async function testSignERC4361Speculos() {
const transport = new SpeculosTransport('http://localhost:5000')
const client = new AppClient(transport);
const acreBtcNew = new AcreBtcNew(client);
const message = "stake.acre.fi wants you to sign in with your Bitcoin account:\nbc1q8fq0vs2f9g52cuk8px9f664qs0j7vtmx3r7wvx\n\n\nURI: https://stake.acre.fi\nVersion: 1\nNonce: cw73Kfdfn1lY42Jj8\nIssued At: 2024-10-01T11:03:05.707Z\nExpiration Time: 2024-10-08T11:03:05.707Z"
const path = "m/44'/0'/0'/0/0";
const result = await acreBtcNew.signERC4361Message({messageHex: Buffer.from(message).toString("hex"), path: path});
console.log(result);
}

function verifyGetWalletPublicKeyResult(
result: { publicKey: string; bitcoinAddress: string; chainCode: string },
expectedXpub: string,
Expand Down Expand Up @@ -318,4 +330,4 @@ class MockClient extends TestingClient {
): string {
return walletPolicy.serialize().toString("hex") + change + addressIndex;
}
}
}

0 comments on commit b17e8cc

Please sign in to comment.