Blog post: https://miguelmota.com/blog/checkpoint-git-commit-objects-to-ethereum-and-verify-with-merkle-proofs/
{ | |
"contractName": "Commits", | |
"abi": [ | |
{ | |
"constant": true, | |
"inputs": [ | |
{ | |
"name": "", | |
"type": "bytes20" | |
} | |
], | |
"name": "checkpoints", | |
"outputs": [ | |
{ | |
"name": "", | |
"type": "uint256" | |
} | |
], | |
"payable": false, | |
"stateMutability": "view", | |
"type": "function" | |
}, | |
{ | |
"anonymous": false, | |
"inputs": [ | |
{ | |
"indexed": true, | |
"name": "sender", | |
"type": "address" | |
}, | |
{ | |
"indexed": true, | |
"name": "commit", | |
"type": "bytes20" | |
} | |
], | |
"name": "Checkpointed", | |
"type": "event" | |
}, | |
{ | |
"constant": false, | |
"inputs": [ | |
{ | |
"components": [ | |
{ | |
"name": "tree", | |
"type": "string" | |
}, | |
{ | |
"name": "parents", | |
"type": "string[]" | |
}, | |
{ | |
"name": "author", | |
"type": "string" | |
}, | |
{ | |
"name": "authorDate", | |
"type": "uint256" | |
}, | |
{ | |
"name": "authorDateTzOffset", | |
"type": "string" | |
}, | |
{ | |
"name": "committer", | |
"type": "string" | |
}, | |
{ | |
"name": "commitDate", | |
"type": "uint256" | |
}, | |
{ | |
"name": "commitDateTzOffset", | |
"type": "string" | |
}, | |
{ | |
"name": "message", | |
"type": "string" | |
}, | |
{ | |
"name": "signature", | |
"type": "string" | |
} | |
], | |
"name": "_commit", | |
"type": "tuple" | |
} | |
], | |
"name": "checkpoint", | |
"outputs": [ | |
{ | |
"name": "commitHash", | |
"type": "bytes20" | |
} | |
], | |
"payable": false, | |
"stateMutability": "nonpayable", | |
"type": "function" | |
}, | |
{ | |
"constant": true, | |
"inputs": [ | |
{ | |
"name": "commit", | |
"type": "bytes20" | |
} | |
], | |
"name": "checkpointed", | |
"outputs": [ | |
{ | |
"name": "", | |
"type": "bool" | |
} | |
], | |
"payable": false, | |
"stateMutability": "view", | |
"type": "function" | |
}, | |
{ | |
"constant": true, | |
"inputs": [ | |
{ | |
"name": "commit", | |
"type": "bytes20" | |
}, | |
{ | |
"name": "root", | |
"type": "bytes20" | |
}, | |
{ | |
"name": "leaf", | |
"type": "bytes20" | |
}, | |
{ | |
"name": "proof", | |
"type": "bytes20[]" | |
} | |
], | |
"name": "checkpointVerify", | |
"outputs": [ | |
{ | |
"name": "", | |
"type": "bool" | |
} | |
], | |
"payable": false, | |
"stateMutability": "view", | |
"type": "function" | |
}, | |
{ | |
"constant": true, | |
"inputs": [ | |
{ | |
"name": "root", | |
"type": "bytes20" | |
}, | |
{ | |
"name": "leaf", | |
"type": "bytes20" | |
}, | |
{ | |
"name": "proof", | |
"type": "bytes20[]" | |
} | |
], | |
"name": "verify", | |
"outputs": [ | |
{ | |
"name": "", | |
"type": "bool" | |
} | |
], | |
"payable": false, | |
"stateMutability": "pure", | |
"type": "function" | |
} | |
], | |
"compiler": { | |
"name": "solc", | |
"version": "0.5.8+commit.23d335f2.Emscripten.clang" | |
}, | |
"networks": { | |
"42": { | |
"events": {}, | |
"links": {}, | |
"address": "0xBA7619DFA824A168fDD76a77b8236f83528d015C", | |
"transactionHash": "0x52554e323a127444d04e84b5f3712f2d168d2c325fc288b7ea4e6da2ebb725c2" | |
} | |
}, | |
"schemaVersion": "3.0.11", | |
"updatedAt": "2019-07-28T10:46:05.506Z", | |
"devdoc": { | |
"methods": {} | |
}, | |
"userdoc": { | |
"methods": {} | |
} | |
} |
const assert = require('assert') | |
const fs = require('fs') | |
const path = require('path') | |
const { execSync } = require('child_process') | |
const Web3 = require('web3') | |
const PrivateKeyProvider = require('truffle-privatekey-provider') | |
const parseCommit = require('git-parse-commit') | |
const contractJSON = JSON.parse(fs.readFileSync(path.resolve(__dirname, './Commits.json'))) | |
const { abi } = contractJSON | |
const networkId = 42 // kovan | |
const { address: contractAddress } = contractJSON.networks[networkId] | |
const privateKey = execSync('git config ethereumcheckpoint.privatekey').toString().trim() | |
const providerUri = execSync('git config ethereumcheckpoint.provideruri').toString().trim() | |
const provider = new PrivateKeyProvider(privateKey, providerUri) | |
const web3 = new Web3(provider) | |
;(async () => { | |
const { address: sender } = web3.eth.accounts.privateKeyToAccount(`0x${privateKey}`) | |
const commit = execSync('git cat-file -p HEAD').toString().trim() | |
const commitHash = execSync('git rev-parse HEAD').toString().trim() | |
const tag = execSync('git describe --tags `git rev-list --tags --max-count=1`').toString().trim() | |
const tagCommit = execSync(`git rev-list -n 1 "${tag}"`).toString().trim() | |
if (tagCommit !== commitHash) { | |
console.log('Tag not found for commit, skipping checkpoint.') | |
process.exit(0) | |
} | |
console.log(`Tag ${tag} found, checkpointing commit ${commitHash}`) | |
const { | |
tree, | |
parents, | |
author: { | |
name: authorName, | |
email: authorEmail, | |
timestamp: authorDate, | |
timezone: authorDateTzOffset | |
}, | |
committer: { | |
name: committerName, | |
email: committerEmail, | |
timestamp: commitDate, | |
timezone: commitDateTzOffset | |
}, | |
pgp, | |
title, | |
description | |
} = parseCommit(`${commitHash}\n${commit}`) | |
const author = `${authorName} <${authorEmail}>` | |
const committer = `${committerName} <${committerEmail}>` | |
const message = `${title}${description} | |
` | |
let signature = '' | |
if (pgp) { | |
// NOTE: newlines are necessary here | |
signature = `-----BEGIN PGP SIGNATURE----- | |
${pgp} | |
-----END PGP SIGNATURE-----`.split('\n').join('\n ') + '\n' | |
} | |
const data = { | |
tree, | |
parents, | |
author, | |
authorDate, | |
authorDateTzOffset, | |
committer, | |
commitDate, | |
commitDateTzOffset, | |
message, | |
signature | |
} | |
const contract = new web3.eth.Contract(abi, contractAddress) | |
try { | |
console.log('Checkpointing commit to Ethereum...') | |
const { | |
status, | |
transactionHash | |
} = await contract.methods.checkpoint(data).send({ | |
from: sender | |
}) | |
console.log(`Transaction hash: ${transactionHash}`) | |
assert.ok(status) | |
const _commitDate = await contract.methods.checkpoints(`0x${commitHash}`).call() | |
assert.equal(_commitDate.toString(), commitDate.toString()) | |
console.log('Successfully checkpointed commit to Ethereum.') | |
process.exit(0) | |
} catch(err) { | |
console.error(err.message) | |
} | |
})() |
{ | |
"name": "ethereum-checkpoint-git-commit", | |
"version": "0.0.1", | |
"dependencies": { | |
"git-parse-commit": "^1.0.0", | |
"web3": "^1.2.0", | |
"truffle-privatekey-provider": "^1.3.0" | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment