-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Adegbite Ademola Kelvin
authored and
Adegbite Ademola Kelvin
committed
Sep 5, 2024
1 parent
eb3eea7
commit 8b32097
Showing
7 changed files
with
252 additions
and
6,187 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
jest.setTimeout(20000); | ||
import StarknetSigner from "../signing/chains/StarknetSigner"; | ||
import { RpcProvider } from "starknet"; | ||
import Crypto from "crypto"; | ||
import { createData } from "../../index"; | ||
|
||
const tagsTestVariations = [ | ||
{ description: "no tags", tags: undefined }, | ||
{ description: "empty tags", tags: [] }, | ||
{ description: "single tag", tags: [{ name: "Content-Type", value: "image/png" }] }, | ||
{ | ||
description: "multiple tags", | ||
tags: [ | ||
{ name: "Content-Type", value: "image/png" }, | ||
{ name: "hello", value: "world" }, | ||
{ name: "lorem", value: "ipsum" }, | ||
], | ||
}, | ||
]; | ||
|
||
const dataTestVariations = [ | ||
{ description: "empty string", data: "" }, | ||
{ description: "small string", data: "hello world" }, | ||
{ description: "large string", data: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()_+-=[]{};':\",./<>?`~" }, | ||
{ description: "empty buffer", data: Buffer.from([]) }, | ||
{ description: "small buffer", data: Buffer.from("hello world") }, | ||
{ description: "large buffer", data: Buffer.from("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()_+-=[]{};':\",./<>?`~") }, | ||
]; | ||
|
||
describe("Typed Starknet Signer", () => { | ||
let signer: StarknetSigner; | ||
const provider = new RpcProvider({ nodeUrl: "https://starknet-sepolia.public.blastapi.io" }); | ||
|
||
const PrivateKey = "0x0570d0ab0e4bd9735277e8db6c8e19918c64ed50423aa5860235635d2487c7bb"; | ||
const myAddressInStarknet = "0x078e47BBEB4Dc687741825d7bEAD044e229960D3362C0C21F45Bb920db08B0c4"; | ||
|
||
beforeAll(async () => { | ||
signer = new StarknetSigner(provider, myAddressInStarknet, PrivateKey); | ||
}); | ||
|
||
it("should sign a known value", async () => { | ||
const data = Buffer.from("Hello-world!"); | ||
const expectedSignature = Buffer.from([ | ||
1, 114, 235, 23, 11, 129, 235, 41, 193, 99, 37, 195, 7, 92, 120, 196, 216, 86, 170, 132, 45, 38, 234, 192, 92, 108, 83, 180, 250, 64, 95, 2, 0, | ||
119, 220, 61, 212, 202, 154, 141, 140, 112, 99, 169, 204, 5, 232, 4, 203, 246, 9, 70, 254, 36, 150, 193, 72, 0, 15, 25, 127, 59, 138, 239, 1, | ||
]); | ||
|
||
const signature = await signer.sign(data); | ||
const signatureBuffer = Buffer.from(signature); | ||
expect(signatureBuffer).toEqual(expectedSignature); | ||
}); | ||
|
||
it("should verify a known values", async () => { | ||
const data = Buffer.from("Hello-world!"); | ||
const signature = await signer.sign(data); | ||
const isValid = await StarknetSigner.verify(signer.publicKey, data, signature); | ||
expect(isValid).toEqual(true); | ||
}); | ||
it("should sign & verify an unknown value", async () => { | ||
const randData = Crypto.randomBytes(256); | ||
const signature = await signer.sign(randData); | ||
const isValid = await StarknetSigner.verify(signer.publicKey, randData, signature); | ||
expect(isValid).toEqual(true); | ||
}); | ||
describe("Create & Validate DataItem", () => { | ||
it("should create a valid dataItem", async () => { | ||
const data = "Hello, Bundlr!"; | ||
const tags = [{ name: "Hello", value: "Bundlr" }]; | ||
const item = createData(data, signer, { tags }); | ||
await item.sign(signer); | ||
expect(await item.isValid()).toBe(true); | ||
}); | ||
|
||
describe("With an unknown wallet", () => { | ||
it("should sign & verify an unknown value", async () => { | ||
const randSigner = new StarknetSigner(provider, myAddressInStarknet, PrivateKey); | ||
const randData = Crypto.randomBytes(256); | ||
const signature = await randSigner.sign(randData); | ||
const isValid = await StarknetSigner.verify(signer.publicKey, randData, signature); | ||
expect(isValid).toEqual(true); | ||
}); | ||
}); | ||
|
||
describe("and given we want to create a dataItem", () => { | ||
describe.each(tagsTestVariations)("with $description tags", ({ tags }) => { | ||
describe.each(dataTestVariations)("and with $description data", ({ data }) => { | ||
it("should create a valid dataItem", async () => { | ||
const item = createData(data, signer, { tags }); | ||
await item.sign(signer); | ||
expect(await item.isValid()).toBe(true); | ||
}); | ||
it("should set the correct tags", async () => { | ||
const item = createData(data, signer, { tags }); | ||
await item.sign(signer); | ||
expect(item.tags).toEqual(tags ?? []); | ||
}); | ||
it("should set the correct data", async () => { | ||
const item = createData(data, signer, { tags }); | ||
await item.sign(signer); | ||
expect(item.rawData).toEqual(Buffer.from(data)); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
import { | ||
Account, | ||
RpcProvider, | ||
Signature, | ||
WeierstrassSignatureType, | ||
ec, | ||
encode, | ||
typedData, | ||
Signer as StarknetSignerAlias, | ||
TypedData, | ||
TypedDataRevision, | ||
} from "starknet"; | ||
import type { Signer } from "../index"; | ||
import { SignatureConfig, SIG_CONFIG } from "../../constants"; | ||
|
||
export default class StarknetSigner implements Signer { | ||
protected signer: Account; | ||
public publicKey: Buffer; | ||
public static address: string; | ||
private static privateKey: string; | ||
public static provider: RpcProvider; | ||
public chainId: string; | ||
readonly ownerLength: number = SIG_CONFIG[SignatureConfig.STARKNET].pubLength; | ||
readonly signatureLength: number = SIG_CONFIG[SignatureConfig.STARKNET].sigLength; | ||
readonly signatureType: number = SignatureConfig.STARKNET; | ||
|
||
// Constructor to set static properties | ||
constructor(provider: RpcProvider, address: string, pKey: string) { | ||
StarknetSigner.provider = provider; | ||
StarknetSigner.address = address; | ||
StarknetSigner.privateKey = pKey; | ||
this.signer = new Account(provider, address, pKey); | ||
} | ||
|
||
public async init() { | ||
try { | ||
const signer = new StarknetSignerAlias(StarknetSigner.privateKey); | ||
const pub_key = await signer.getPubKey(); | ||
let hexKey = pub_key.startsWith("0x") ? pub_key.slice(2) : pub_key; | ||
this.publicKey = Buffer.from(0 + hexKey, "hex"); | ||
this.chainId = await StarknetSigner.provider.getChainId(); | ||
} catch (error) { | ||
console.error("Error setting public key or chain ID:", error); | ||
} | ||
} | ||
|
||
async sign(message: Uint8Array, _opts?: any): Promise<Uint8Array> { | ||
if (!this.publicKey) { | ||
this.init(); | ||
} | ||
if (!this.signer.signMessage) throw new Error("Selected signer does not support message signing"); | ||
let chainId = await this.signer.getChainId(); | ||
let message_to_felt = convertToFelt252(message); | ||
let TypedDataMessage = typed_domain({ chainId: chainId, message: message_to_felt }); | ||
let signature: Signature = (await this.signer.signMessage(TypedDataMessage.typemessage)) as WeierstrassSignatureType; | ||
|
||
const r = BigInt(signature.r).toString(16).padStart(64, "0"); | ||
const s = BigInt(signature.s).toString(16).padStart(64, "0"); | ||
// @ts-ignore | ||
const recovery = signature.recovery.toString(16).padStart(2, "0"); | ||
|
||
const rArray = Uint8Array.from(Buffer.from(r, "hex")); | ||
const sArray = Uint8Array.from(Buffer.from(s, "hex")); | ||
const recoveryArray = Uint8Array.from(Buffer.from(recovery, "hex")); | ||
|
||
const result = new Uint8Array(rArray.length + sArray.length + recoveryArray.length); | ||
result.set(rArray); | ||
result.set(sArray, rArray.length); | ||
result.set(recoveryArray, rArray.length + sArray.length); | ||
|
||
return result; | ||
} | ||
|
||
static async verify(_pk: Buffer, message: Uint8Array, _signature: Uint8Array, _opts?: any): Promise<boolean> { | ||
let chainId = await this.provider.getChainId(); | ||
let message_to_felt = convertToFelt252(message); | ||
let TypedDataMessage = typed_domain({ chainId, message: message_to_felt }); | ||
|
||
const fullPubKey = encode.addHexPrefix(encode.buf2hex(ec.starkCurve.getPublicKey(StarknetSigner.privateKey, true))); | ||
|
||
const msgHash = typedData.getMessageHash(TypedDataMessage.typemessage, StarknetSigner.address); | ||
|
||
return ec.starkCurve.verify(_signature.slice(0, -1), msgHash, fullPubKey); | ||
} | ||
} | ||
|
||
// Utility function to convert Uint8Array to felt252 | ||
function convertToFelt252(data: Uint8Array): bigint[] { | ||
const felt252Array: bigint[] = []; | ||
const felt252Size = 31; | ||
|
||
for (let i = 0; i < data.length; i += felt252Size) { | ||
let value = BigInt(0); | ||
for (let j = 0; j < felt252Size && i + j < data.length; j++) { | ||
value = (value << BigInt(8)) | BigInt(data[i + j]); | ||
} | ||
felt252Array.push(value); | ||
} | ||
|
||
return felt252Array; | ||
} | ||
|
||
interface TypedParam { | ||
chainId: string | number; | ||
message: bigint[]; | ||
} | ||
|
||
export const typed_domain = ({ chainId, message }: TypedParam): { typemessage: TypedData } => { | ||
const typemessage: TypedData = { | ||
domain: { | ||
name: "Arbundle", | ||
chainId: chainId, | ||
version: "1.0.2", | ||
revision: TypedDataRevision.ACTIVE, | ||
}, | ||
message: { | ||
message, | ||
}, | ||
primaryType: "Message", | ||
types: { | ||
Message: [{ name: "message", type: "felt*" }], | ||
StarknetDomain: [ | ||
{ name: "name", type: "string" }, | ||
{ name: "chainId", type: "felt" }, | ||
{ name: "version", type: "string" }, | ||
], | ||
}, | ||
}; | ||
return { | ||
typemessage, | ||
}; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.