Skip to content

Instantly share code, notes, and snippets.

@a2468834
Last active September 26, 2024 16:13
Show Gist options
  • Save a2468834/17dc14ed6e64a30b3df5e44c5da04314 to your computer and use it in GitHub Desktop.
Save a2468834/17dc14ed6e64a30b3df5e44c5da04314 to your computer and use it in GitHub Desktop.
import { Address, beginCell } from "@ton/ton";
import { mnemonicToPrivateKey, sign, sha256, signVerify } from "@ton/crypto";
import assert from "assert";
// Type
type LessThan<N extends number, A extends any[] = []> = N extends A['length'] ? A[number] : LessThan<N, [...A, A['length']]>;
type RangeOf<F extends number, T extends number> = Exclude<T | LessThan<T>, LessThan<F>>
type AtomicType = `int${RangeOf<0, 257>}` | `uint${RangeOf<0, 256>}` | "slice" | "cell";
type TypeDefinition = Record<string, Array<{ name: string, type: string | AtomicType }>>;
// Auxiliary function
const linearization = (primaryType: string, typeDefinition: TypeDefinition, found: string[] = []) => {
if (found.includes(primaryType)) {
return found;
} else if (typeDefinition[primaryType] === undefined) {
return found;
}
found.push(primaryType);
for (const property of typeDefinition[primaryType]) {
for (const child of linearization(property.type, typeDefinition, found)) {
if (!found.includes(child)) {
found.push(child);
}
}
}
return found;
};
const encodeType = (primaryType: string, typeDefinition: TypeDefinition) => {
const c3LinearResult = linearization(primaryType, typeDefinition);
return c3LinearResult.map(
eachType =>
`${eachType}(${typeDefinition[eachType]
.map(({ name, type }) => `${type} ${name}`)
.join(',')})`
).join('');
};
const stringToUint32 = async (str: string) => {
const hashString = (await sha256(str)).toString('hex', 0, 4);
return BigInt(`0x${hashString}`);
};
const bufferToBigInt = (buf: Buffer) => {
return BigInt(`0x${buf.toString('hex')}`);
};
const getWorkchainId = (addressLike: string) => {
return Address.parse(addressLike).workChain;
};
const getAccountId = (addressLike: string) => {
return '0x' + Address.parse(addressLike).hash.toString('hex');
};
/////////////////////////////// Example test data //////////////////////////////
const signer = mnemonicToPrivateKey([...Array(24)].map(each => "food"));
const verifier = "0QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACkT"
const testVector = {
typeDefinition: {
TEP181Domain: [
{ name: 'name', type: 'uint32' },
{ name: 'version', type: 'uint32' },
{ name: 'workchainId', type: 'int8' },
{ name: 'verifierContract', type: 'uint256' }
],
Permit: [
{ name: 'owner', type: 'Address' },
{ name: 'spender', type: 'Address' },
{ name: 'value', type: 'uint32' },
{ name: 'nonce', type: 'uint32' },
{ name: 'deadline', type: 'uint32' },
],
Address: [
{ name: 'workchainId', type: 'int8' },
{ name: 'accountId', type: 'uint256' },
]
},
primaryType: 'Permit',
typeData: {
tep181Domain: {
name: stringToUint32('Permit Ticket'),
version: stringToUint32('1'),
workchainId: getWorkchainId(verifier),
verifierContract: getAccountId(verifier)
},
message: {
owner: {
workchainId: BigInt(0),
accountId: BigInt('0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff')
},
spender: {
workchainId: BigInt(0),
accountId: BigInt('0x0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f')
},
value: BigInt(123),
nonce: BigInt(456),
deadline: BigInt(0xffffffff)
}
}
};
const CONSTANT = {
PERMIT_TYPEHASH: sha256(encodeType('Permit', testVector.typeDefinition)),
ADDRESS_TYPEHASH: sha256(encodeType('Address', testVector.typeDefinition)),
TEP181DOMAIN_TYPEHASH: sha256(encodeType('TEP181Domain', testVector.typeDefinition)),
PREFIX: Buffer.concat([Buffer.from("ff", "hex"), Buffer.from("ff", "hex")]),
EXPECTED_SIG: "feT5H+PPaB7IlIiqGF0UDcE5kFgKix7sygGLwHMeq1fPzO3m01SI/me/45kZ3W3LRIiNpJobMmi/pKj6TuCEBQ=="
};
////////////////////////////////////////////////////////////////////////////////
const main = async () => {
const owner_hash = beginCell()
.storeUint(bufferToBigInt(await CONSTANT.ADDRESS_TYPEHASH), 256)
.storeInt(testVector.typeData.message.owner.workchainId, 8)
.storeUint(BigInt(testVector.typeData.message.owner.accountId), 256)
.endCell().hash();
const spender_hash = beginCell()
.storeUint(bufferToBigInt(await CONSTANT.ADDRESS_TYPEHASH), 256)
.storeInt(testVector.typeData.message.spender.workchainId, 8)
.storeUint(BigInt(testVector.typeData.message.spender.accountId), 256)
.endCell().hash();
const struct_hash = beginCell()
.storeUint(bufferToBigInt(await CONSTANT.PERMIT_TYPEHASH), 256)
.storeUint(bufferToBigInt(owner_hash), 256)
.storeUint(bufferToBigInt(spender_hash), 256)
.storeUint(testVector.typeData.message.value, 32)
.storeUint(testVector.typeData.message.nonce, 32)
.storeUint(testVector.typeData.message.deadline, 32)
.endCell().hash();
const domain_separator_hash = beginCell()
.storeUint(bufferToBigInt(await CONSTANT.TEP181DOMAIN_TYPEHASH), 256)
.storeUint(await testVector.typeData.tep181Domain.name, 32)
.storeUint(await testVector.typeData.tep181Domain.version, 32)
.storeInt(testVector.typeData.tep181Domain.workchainId, 8)
.storeUint(BigInt(testVector.typeData.tep181Domain.verifierContract), 256)
.endCell().hash();
const hashedData = beginCell()
.storeUint(bufferToBigInt(CONSTANT.PREFIX), 16)
.storeUint(bufferToBigInt(domain_separator_hash), 256)
.storeUint(bufferToBigInt(struct_hash), 256)
.endCell().hash();
const signature = sign(
hashedData,
(await signer).secretKey
);
assert(signature.toString("base64") === CONSTANT.EXPECTED_SIG);
assert(signVerify(hashedData, signature, (await signer).publicKey) === true);
};
main().then();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment