Skip to content

Commit

Permalink
makes checksum,upload,verify argument aware
Browse files Browse the repository at this point in the history
adds samples for lit, terms acceptance
http sample call for signoff

Signed-off-by: stadolf <stadolf@gmail.com>
  • Loading branch information
elmariachi111 committed Sep 8, 2023
1 parent 99683e8 commit ea9c25a
Show file tree
Hide file tree
Showing 12 changed files with 1,697 additions and 28 deletions.
3 changes: 2 additions & 1 deletion .env.sample
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
W3S_TOKEN=
W3S_TOKEN=
PRIVATE_KEY=
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,10 @@ web_modules/
out

dist
file.enc

# Stores VSCode versions used for testing VSCode extensions
.vscode-test

uploads/*
!.gitkeep
29 changes: 24 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,27 +1,46 @@
# Demo code for IP-NFT implementers

Some of these examples rely on an API key for [web3.storage](https://web3.storage). Get one and put it in a local .env file.
Some of these examples rely on an API key for [web3.storage](https://web3.storage). Get one and put it in a local ` .env` file.
For the crypto-related scripts to run you need add a _private key_ of an Ethereum account as `PRIVATE_KEY`to the local`.env` file. You can copy this from your Metamask wallet.

**!!!!NEVER USE ACCOUNTS THAT YOU ALSO WOULD USE ON A MAIN NET HERE!!!!**

## Checksums

Compute and verify a file's checksum using [multiformats](https://www.npmjs.com/package/multiformats)

```
yarn ts-node ./checksum.mts
yarn ts-node ./checksum.mts <file>
```

## Uploads

uploads sample files to IPFS using web3.storage
uploads files to IPFS using web3.storage

```
yarn ts-node ./upload.mts <file>
```

## Lit encryption

Reads and encrypts the provided file. Requires PRIVATE_KEY to sign in with Lit

```
yarn ts-node ./lit.mts <agreement.pdf>
```

## terms signature

Reads the agreements from a preliminary ipnft.json file and assembles and signs the required terms acceptance message

```
yarn ts-node ./upload.mts
yarn ts-node ./terms.mts <current-ipnft-json>
```

## Schema verification

retrieves JSON schema and IP-NFT metadata from a public location and verifies its validity

```
yarn ts-node ./verifyMetadata.mts
yarn ts-node ./verifyMetadata.mts ipfs://bafy...
```
8 changes: 6 additions & 2 deletions checksum.mts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { CID } from "multiformats/cid";
import * as json from "multiformats/codecs/json";
import { sha256 } from "multiformats/hashes/sha2";
import { promises as fs } from "node:fs";

const checksum = async (u8: Uint8Array) => {
//https://multiformats.io/multihash/
Expand All @@ -21,8 +22,11 @@ const verifyChecksum = async (
};

(async () => {
const binaryContent = new TextEncoder().encode("This is the content");
const filePath = process.argv[2];
//const binaryContent = new TextEncoder().encode("This is the content");
const binaryContent = await fs.readFile(filePath);

const cid = await checksum(binaryContent);
const valid = await verifyChecksum(binaryContent, cid.toString());
console.log(cid, valid);
console.log(cid, binaryContent.length, valid);
})();
142 changes: 142 additions & 0 deletions lit.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import dotenv from "dotenv";
import * as ethers from "ethers";
import siwe from "siwe";
import LitJsSdk, {
LitNodeClientNodeJs,
} from "@lit-protocol/lit-node-client-nodejs";
import { hexlify, Wallet } from "ethers";
import { promises as fs } from "node:fs";

dotenv.config();

const createWallet = (privateKey: string) => {
return new ethers.Wallet(privateKey);
};

const obtainAuthSig = async (wallet: Wallet) => {
const address = ethers.getAddress(await wallet.getAddress());

// Craft the SIWE message
const domain = "localhost";
const origin = "https://localhost/login";
const statement = "This proves that we're controlling our address";
const siweMessage = new siwe.SiweMessage({
domain,
address: address,
statement,
uri: origin,
version: "1",
chainId: 5,
});
const messageToSign = siweMessage.prepareMessage();

// Sign the message and format the authSig
const signature = await wallet.signMessage(messageToSign);

return {
sig: signature,
derivedVia: "web3.eth.personal.sign",
signedMessage: messageToSign,
address: address,
};
};

//see https://developer.litprotocol.com/v2/accessControl/EVM/customContractCalls#must-posess-at-least-one-erc1155-token-with-a-given-token-id
const makeAccessControlConditions = (ipnftId: string) => {
return [
{
contractAddress: "0xaf7358576C9F7cD84696D28702fC5ADe33cce0e9",
chain: "goerli",
functionName: "canRead",
functionParams: [":userAddress", ipnftId],
functionAbi: {
inputs: [
{
internalType: "address",
name: "reader",
type: "address",
},
{
internalType: "uint256",
name: "tokenId",
type: "uint256",
},
],
name: "canRead",
outputs: [
{
internalType: "bool",
name: "",
type: "bool",
},
],
stateMutability: "view",
type: "function",
},
returnValueTest: {
key: "",
comparator: "=",
value: "true",
},
},
];
};

const encryptWithLit = async (
client: LitNodeClientNodeJs,
binaryContent: Blob,
wallet: Wallet
) => {
//note that the symmetric key is visible here!
const { encryptedFile, symmetricKey } = await LitJsSdk.encryptFile({
file: binaryContent,
});

const authSig = await obtainAuthSig(wallet);
const accessControlConditions = makeAccessControlConditions("32");

const encryptedSymmetricKey = await client.saveEncryptionKey({
accessControlConditions,
symmetricKey,
authSig,
chain: "goerli",
});

return { accessControlConditions, encryptedFile, encryptedSymmetricKey };
};

const main = async () => {
const filePath = process.argv[2];
//const binaryContent = new TextEncoder().encode("This is the content");
const fileContent = await fs.readFile(filePath);

const client = new LitJsSdk.LitNodeClientNodeJs({});
await client.connect();

const wallet = createWallet(process.env.PRIVATE_KEY as string);

const binaryContent = new Blob([fileContent], {
type: "application/octet-stream",
});

const { accessControlConditions, encryptedFile, encryptedSymmetricKey } =
await encryptWithLit(client, binaryContent, wallet);

await fs.writeFile(
"./uploads/file.enc",
Buffer.from(await encryptedFile.arrayBuffer())
);

console.log(
JSON.stringify(
{
accessControlConditions,
encryptedSymmetricKey: hexlify(encryptedSymmetricKey),
},
null,
2
)
);
};

main();
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@
"main": "index.js",
"license": "MIT",
"dependencies": {
"@lit-protocol/lit-node-client-nodejs": "^2.2.54",
"ajv": "^8.12.0",
"ajv-formats": "^2.1.1",
"dotenv": "^16.3.1",
"ethers": "^6.7.1",
"multiformats": "^12.0.1",
"siwe": "^2.1.4",
"ts-node": "^10.9.1",
"typescript": "^4.9.5",
"web3.storage": "^4.5.5"
Expand Down
10 changes: 10 additions & 0 deletions signoff.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
POST https://testnet.mint.molecule.to/api/signoffMetadata
Content-Type: application/json

{
"network": "goerli",
"minter": "0xd1f5B9Dc9F5d55523aB25839f8785aaC74EDE98F",
"to": "0xd1f5B9Dc9F5d55523aB25839f8785aaC74EDE98F",
"reservationId": 2,
"tokenURI": "ipfs://bafkreihsjl25u5irbp33t3ntwwrvsaulajzxbieqpugkcan5bo3bm4romq"
}
60 changes: 60 additions & 0 deletions terms.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import dotenv from "dotenv";
import * as ethers from "ethers";

import { promises as fs } from "node:fs";

dotenv.config();

type TermsMessageV1Parameters = {
version: string;
chainId: string;
agreements: Array<{ type: string; content_hash: string }>;
};

const TermsMessageV1 =
"I accept the IP-NFT minting terms\n" +
"\nI have read and agreed to the terms of the IP-NFT Assignment Agreement" +
"\nI understand that the IP-NFT represents legal rights to IP and data of the project in my Research Agreement" +
"\nI understand that this in an irreversible and publicly traceable transaction on the Ethereum Blockchain" +
"\nI understand this is beta software and agree to the Terms of Service and assume all risks of minting this IP-NFT" +
"\n" +
"\n{agreements}" +
"\n" +
"\nVersion: {version}" +
"\nChain ID: {chain-id}";

export const TermsMessage = (parameters: TermsMessageV1Parameters) =>
TermsMessageV1.replace(
"{agreements}",
parameters.agreements
.map((a) => `${a.type} Hash: ${a.content_hash}`)
.join("\n")
)
.replace("{version}", parameters.version)
.replace("{chain-id}", parameters.chainId);

const createWallet = (privateKey: string) => {
return new ethers.Wallet(privateKey);
};

const main = async () => {
const filePath = process.argv[2];
const utfContent = await fs.readFile(filePath, "utf-8");
const metadata = JSON.parse(utfContent);
const agreements = metadata.properties.agreements;

const message = TermsMessage({
agreements,
chainId: "5",
version: "1",
});
console.log("Terms Message\n", message);

const wallet = createWallet(process.env.PRIVATE_KEY as string);
//create an EIP-191 signed message
const termsSig = await wallet.signMessage(message);

console.log(`Terms Signature (by ${wallet.address}):`, termsSig);
};

main();
12 changes: 6 additions & 6 deletions upload.mts
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
import dotenv from "dotenv";
import { Blob } from "node:buffer";
import { Web3Storage } from "web3.storage";
import { promises as fs } from "node:fs";
dotenv.config();

const w3sClient = new Web3Storage({ token: process.env.W3S_TOKEN as string });

(async () => {
const content = {
uploaded_at: new Date().toISOString(),
};
const filePath = process.argv[2];
const utfContent = await fs.readFile(filePath, "utf-8");

const binaryContent = new Blob([JSON.stringify(content)], {
const binaryContent = new Blob([utfContent], {
type: "application/json",
});

const file = {
name: "filename.json",
name: "metadata.json",
stream: () => binaryContent.stream(),
};

//@ts-ignore
const cid = await w3sClient.put([file], {
name: "some.json",
name: "metadata.json",
wrapWithDirectory: false,
});
console.log(cid);
Expand Down
Empty file added uploads/.gitkeep
Empty file.
10 changes: 4 additions & 6 deletions verifyMetadata.mts
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
/// <reference lib="dom" />
import dotenv from "dotenv";
import Ajv from "ajv";
import addFormats from "ajv-formats";
dotenv.config();
import dotenv from "dotenv";

//https://testnets.opensea.io/assets/goerli/0xaf7358576C9F7cD84696D28702fC5ADe33cce0e9/73
const ipnftMetadataUri =
"ipfs://bafkreicnc6ilzauc5e5dhyakk3e6bx3gsre7qtp4nq3b3utjv4fr4oa5f4";
dotenv.config();

async function retrieveFromIpfs(ipfsLink: string): Promise<any> {
const ipfsGatewayUrl = ipfsLink.replace("ipfs://", "https://ipfs.io/ipfs/");
Expand All @@ -19,7 +16,8 @@ async function retrieveFromIpfs(ipfsLink: string): Promise<any> {
"ipfs://bafybeihvql52zxnkksedcad5i6ptimpquu4oiek56ojldkm3ndkfoevmf4/ipnft.schema.json"
);

const document = await retrieveFromIpfs(ipnftMetadataUri);
const ipfsLocation = process.argv[2];
const document = await retrieveFromIpfs(ipfsLocation);

const ajv = new Ajv();
addFormats(ajv);
Expand Down
Loading

0 comments on commit ea9c25a

Please sign in to comment.