gitmark 0.0.67 → 0.0.69
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +19 -0
- package/BURN.md +11 -0
- package/CRYPTO.md +27 -0
- package/GENESIS.md +43 -7
- package/MARK.md +38 -0
- package/OPS.md +31 -0
- package/README.md +20 -20
- package/SCHEMA.md +25 -0
- package/TOPUP.md +15 -0
- package/USE_CASES.md +67 -0
- package/bin/git-mark-init +1 -0
- package/bin/git-mark.js +456 -0
- package/package.json +16 -22
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"permissions": {
|
|
3
|
+
"allow": [
|
|
4
|
+
"WebSearch",
|
|
5
|
+
"WebFetch(domain:www.rgbfaq.com)",
|
|
6
|
+
"WebFetch(domain:rgb.info)",
|
|
7
|
+
"Bash(npm install)",
|
|
8
|
+
"Bash(npm search txo_parser)",
|
|
9
|
+
"Bash(../gitmark/bin/git-mark)",
|
|
10
|
+
"Bash(../gitmark/bin/git-mark --genesis abc123:0)"
|
|
11
|
+
],
|
|
12
|
+
"deny": [],
|
|
13
|
+
"ask": [],
|
|
14
|
+
"defaultMode": "acceptEdits",
|
|
15
|
+
"additionalDirectories": [
|
|
16
|
+
"/home/melvin/remote/github.com/solidpayorg/solidpayorg"
|
|
17
|
+
]
|
|
18
|
+
}
|
|
19
|
+
}
|
package/BURN.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
## Introduction
|
|
2
|
+
|
|
3
|
+
Burn is an optional garbage collection operation. Burn is when you want to and a git mark tracking chain.
|
|
4
|
+
|
|
5
|
+
## Transaction
|
|
6
|
+
|
|
7
|
+
The best way to do this is to send vout 0 back to the orginal pubkey, creating a tweak of 0 which is impossible.
|
|
8
|
+
|
|
9
|
+
## Conclusion
|
|
10
|
+
|
|
11
|
+
This optional operation can clean up a chain and freeze it in time
|
package/CRYPTO.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Crypto
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
`version 0.0.2`
|
|
6
|
+
|
|
7
|
+
This page describes the on-chain primitives and mappings used in gitmark [SCHEMA](./SCHEMA.md)
|
|
8
|
+
|
|
9
|
+
# Gitmark Transitions: Bridging Blockchain and Git
|
|
10
|
+
|
|
11
|
+
Gitmark transitions offer a novel approach to embedding on-chain commitments within the blockchain, enabling a seamless capture of changes from one transaction to another. This methodology leverages a unique process termed as a **"tweaked spend to self"** to modify a transaction's output (TXO), enriching it with additional data while ensuring the original asset holder retains ownership.
|
|
12
|
+
|
|
13
|
+
## How It Works
|
|
14
|
+
|
|
15
|
+
- **Initial State (Address 1)**: Identified as `A`, this represents the blockchain address prior to modification.
|
|
16
|
+
- **Transformed State (Address 2)**: Denoted as `A + T`, this address emerges post-modification, with `T` symbolizing the tweak applied to `A`. This tweak is not arbitrary; it encapsulates specific, transition-relevant information.
|
|
17
|
+
|
|
18
|
+
The new address has a vout of 0. All other vouts are not used in gitmark, they may be used to send funds elsewhere.
|
|
19
|
+
|
|
20
|
+
### The Role of `T`
|
|
21
|
+
|
|
22
|
+
`T` stands out as it is meticulously designed to serve as an on-chain commitment, directly alluding to a distinct Git commit. The core of `T`'s value lies in its equivalence to the latest Git hash. This equivalence forges a verifiable nexus between the blockchain transaction and a specific snapshot of the Git repository.
|
|
23
|
+
|
|
24
|
+
### Implications
|
|
25
|
+
|
|
26
|
+
This intricate connection ensures the blockchain's role extends beyond the realm of financial transactions, positioning it as a robust, unalterable ledger for software development benchmarks through Git commits. Gitmark transitions herald a new era of transparency and auditability in software development. They exploit blockchain's core strengths—trust and integrity—to solidify digital records' credibility.
|
|
27
|
+
|
package/GENESIS.md
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
## Genesis
|
|
2
2
|
|
|
3
|
+
### Prerequisites
|
|
4
|
+
|
|
3
5
|
The genesis transaction output starts the chain of marks in git mark
|
|
4
6
|
|
|
5
|
-
In order to start marking you will need a genesis transaction output
|
|
7
|
+
In order to start marking you will need a genesis transaction output aka uxto
|
|
6
8
|
|
|
7
9
|
This will also have a public address and a private key (secret exponent)
|
|
8
10
|
|
|
9
|
-
You will need the transaction id and the
|
|
11
|
+
You will need the **transaction output id** and the **private key** to start git marking
|
|
12
|
+
|
|
13
|
+
### Storing Secrets
|
|
10
14
|
|
|
11
15
|
One way to save the secret exponent is in git
|
|
12
16
|
|
|
@@ -14,7 +18,7 @@ One way to save the secret exponent is in git
|
|
|
14
18
|
git config gitmark.secret <secretexponent>
|
|
15
19
|
```
|
|
16
20
|
|
|
17
|
-
This is not terribly secure, but for small projects to get started it is convenient
|
|
21
|
+
This is not terribly secure, but for small projects to get started it is convenient. An article on trade offs of this approach is presented here [here](https://withblue.ink/2021/05/07/storing-secrets-and-passwords-in-git-is-bad.html)
|
|
18
22
|
|
|
19
23
|
A useful way to generate an address and secret exponent would be:
|
|
20
24
|
|
|
@@ -22,28 +26,60 @@ https://project-bitmark.github.io/brain/
|
|
|
22
26
|
|
|
23
27
|
Use a very secure password for anything more than testing
|
|
24
28
|
|
|
29
|
+
### Funding
|
|
30
|
+
|
|
25
31
|
Once you have an address, send some coins there from a faucet, a friend, or by being marked
|
|
26
32
|
|
|
27
33
|
The genesis id also doubles as the @id for a gitmark project
|
|
28
34
|
|
|
29
|
-
|
|
35
|
+
|
|
36
|
+
### Gitmark.json
|
|
37
|
+
|
|
38
|
+
Optionally a file can be added, `gitmark.json`, in the root directory of your repo
|
|
30
39
|
|
|
31
40
|
It may look like this:
|
|
32
41
|
|
|
33
42
|
```JSON
|
|
34
43
|
{
|
|
35
|
-
"@id": "gitmark:
|
|
36
|
-
"genesis": "gitmark:
|
|
44
|
+
"@id": "gitmark:59ff24db0321cb6b32a404815345b00ae68ca8b81150fbea6464ee10557e0fae:1",
|
|
45
|
+
"genesis": "gitmark:59ff24db0321cb6b32a404815345b00ae68ca8b81150fbea6464ee10557e0fae:1",
|
|
37
46
|
"nick": "myrepo",
|
|
38
47
|
"package": "./package.json",
|
|
48
|
+
"pubkey": "7574866ae7653e084c3c8e9e6359660e2c728d249fefb0f984bd32393f2ac67f",
|
|
39
49
|
"repository": "./"
|
|
40
50
|
}
|
|
41
51
|
```
|
|
42
52
|
|
|
53
|
+
- **@id** is the unspent transaction output
|
|
54
|
+
- **genesis** points to the same transaction output
|
|
55
|
+
- **nick** is a short name e.g. as used in npm
|
|
56
|
+
- **package** points to a package.json info
|
|
57
|
+
- **pubkey** is the pubkey for the genesis tx, in hex
|
|
58
|
+
- **repository** is an absolute or relative hint as to where to find the git code
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
### First git mark
|
|
62
|
+
|
|
43
63
|
Once you have your genesis tx, you can make your first git mark by running, for example:
|
|
44
64
|
|
|
45
65
|
```
|
|
46
66
|
git mark --genesis b1fb9acb83f85887760b2e1a71e1df370976b1596be101bb0dbe8fd1c80f91cd:0
|
|
47
67
|
```
|
|
48
68
|
|
|
49
|
-
Do this after you have
|
|
69
|
+
Do this after you have committed your first files, and as recommended a gitmark.json file too
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
### Notes on Commits
|
|
73
|
+
|
|
74
|
+
The commit requires a name, email and commit message, with optional signing.
|
|
75
|
+
|
|
76
|
+
The **name** can be whatever you want
|
|
77
|
+
|
|
78
|
+
The **email** is open, but you can select a noreply address to indicate privacy, such as noreply@<genesis-hash>.gitmark
|
|
79
|
+
|
|
80
|
+
The **commit message** can be anything, and will be the first commit ready to be marked. e.g. "first"
|
|
81
|
+
|
|
82
|
+
### Notes on .gitmark
|
|
83
|
+
|
|
84
|
+
The .gitmark address is just a place holder if it is undesirable to enter a public email address. In future it may be possible to resolve .gitmark URLs to a working GitHub repository and deployments
|
|
85
|
+
|
package/MARK.md
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+

|
|
2
|
+
|
|
3
|
+
## Introduction
|
|
4
|
+
|
|
5
|
+
Mark is the main function of gitmark. It has a prerequisite on [GENESIS](./GENESIS.md) and
|
|
6
|
+
with these two oprations you are able to create robust git repos recorded in time, and
|
|
7
|
+
identifiable globally.
|
|
8
|
+
|
|
9
|
+
## Chain follows repo
|
|
10
|
+
|
|
11
|
+
A mark is a marking of the underlying block chain with an on-chain committment
|
|
12
|
+
|
|
13
|
+
The transaction is tweeked with the new git hash, according to [CRYPTO](./CRYPTO.md)
|
|
14
|
+
|
|
15
|
+
In essense, this proves the chain is following the github repo and provides a timestamp server
|
|
16
|
+
|
|
17
|
+
## Repo follows chain
|
|
18
|
+
|
|
19
|
+
In order to create a two-way relationship the repo also follows the chain by recording the most recent
|
|
20
|
+
transaction object (txo) in a well known location.
|
|
21
|
+
|
|
22
|
+
## TXO well known location
|
|
23
|
+
|
|
24
|
+
The txo well known location is in the directory `.txo`
|
|
25
|
+
|
|
26
|
+
In there is a file txo.txt which is of the form:
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
<txo_uri> <balance>
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The balance is an amount in satoshis
|
|
33
|
+
|
|
34
|
+
## Conclusion
|
|
35
|
+
|
|
36
|
+
In this way, the chain follows the repository, and the repository follows the chain,
|
|
37
|
+
leading to a robust, auditable, time-stamping solution using Git and blockchains.
|
|
38
|
+
This approach prevents double-spending by ensuring that the repository and the blockchain are in sync with each other.
|
package/OPS.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
## Gitmark Operations
|
|
2
|
+
|
|
3
|
+
### Genesis
|
|
4
|
+
|
|
5
|
+
Genesis is the utxo used to create a reference to the project
|
|
6
|
+
|
|
7
|
+
[Docs](./GENESIS.md)
|
|
8
|
+
|
|
9
|
+
### Mark
|
|
10
|
+
|
|
11
|
+
Marks a super commit by tweaking a spend to self transaction by the by commit hash
|
|
12
|
+
|
|
13
|
+
[Docs](./MARK.md)
|
|
14
|
+
|
|
15
|
+
### Topup
|
|
16
|
+
|
|
17
|
+
Tops up a seal, can be combined with Mark
|
|
18
|
+
|
|
19
|
+
[Docs](./MARK.md)
|
|
20
|
+
|
|
21
|
+
### Transfer
|
|
22
|
+
|
|
23
|
+
Transfers ownership of the contract to another party
|
|
24
|
+
|
|
25
|
+
[Docs](./TRANSFER.md)
|
|
26
|
+
|
|
27
|
+
### Burn
|
|
28
|
+
|
|
29
|
+
Marks a repo as inactive
|
|
30
|
+
|
|
31
|
+
[Docs](./BURN.md)
|
package/README.md
CHANGED
|
@@ -13,17 +13,15 @@ Mark your git commits, to create global consensus, and a definitive project hist
|
|
|
13
13
|
</div>
|
|
14
14
|
|
|
15
15
|
---
|
|
16
|
-
|
|
17
16
|
|
|
18
17
|
[](https://github.com/solidpayorg/git-mark/blob/gh-pages/LICENSE)
|
|
19
18
|

|
|
20
19
|
[](https://npmjs.com/package/gitmark)
|
|
21
20
|
[](https://github.com/solidpayorg/gitmark/)
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
|
|
24
22
|
## Introduction
|
|
25
23
|
|
|
26
|
-
Gitmark
|
|
24
|
+
Gitmark enhances git functionality by introducing the ability to 'mark' commits using the `git mark` command, integrating supported time chains (aka block chains). This process 'reinforces' or 'finalizes' specific commits, facilitating the establishment of a global consensus and creating a definitive, auditable, and tamper-proof history for a project. Git mark additionally solves the double spend problem for git repositories. Technical details are presented in the git mark [SCHEMA](./SCHEMA.md).
|
|
27
25
|
|
|
28
26
|
## Installation
|
|
29
27
|
|
|
@@ -41,7 +39,7 @@ git mark [--genesis txoutput] # used for the genesis commit
|
|
|
41
39
|
|
|
42
40
|
## Motivation
|
|
43
41
|
|
|
44
|
-
Gitmark
|
|
42
|
+
Gitmark was originally created to facilitate the [marking](https://github.com/project-bitmark/marking/wiki) use case, which aims to allow global, distributed reputation trees to be grounded in a blockchain.
|
|
45
43
|
|
|
46
44
|
What is made possible, is a way to provide consensus on a definitive git branch/chain, in order to ensure that the history has not been tampered with
|
|
47
45
|
|
|
@@ -49,7 +47,7 @@ One can reconstruct the current state from the history, and this can also be use
|
|
|
49
47
|
|
|
50
48
|
It's also possible to audit and verify the integrity of the git chain, to create a secure, finalized, state machine, with a definitive head, that is globally synced
|
|
51
49
|
|
|
52
|
-
The system can be extended beyond reputation trees, to use any git based store, and anchor it to a secure,
|
|
50
|
+
The system can be extended beyond reputation trees, to use any git based store, and anchor it to a secure, verifiable chain of blocks, to determine the definitive history
|
|
53
51
|
|
|
54
52
|
Many thanks go to Peter Todd for his work on [single use seals](https://petertodd.org/2017/scalable-single-use-seal-asset-transfer) and Dr Maxim Orlovsky for his work on [RGB](https://rgb-org.github.io/)
|
|
55
53
|
|
|
@@ -57,19 +55,17 @@ _Gitmark is pre-alpha software, it should be considered experimental, and used a
|
|
|
57
55
|
|
|
58
56
|
## Prerequisites
|
|
59
57
|
|
|
60
|
-
Because
|
|
61
|
-
|
|
62
|
-
Gitmark does not support projects that are premines, instamines, ICOs, have developer taxes or provably unfair consensus, such as proof of stake
|
|
58
|
+
Because Gitmark was designed to anchor reputation trees, the reputation of the underlying blockchain must be unimpaired. Gitmark only supports blockchains that are provably fair. Bitcoin is regarded as the most secure and fairest of all blockchains, and should be used for high value projects where cost is not an issue.
|
|
63
59
|
|
|
64
|
-
|
|
60
|
+
Gitmark does not support projects that are premines, instamines, ICOs, have developer taxes or provably unfair consensus, such as proof of stake.
|
|
65
61
|
|
|
66
|
-
|
|
62
|
+
In solving the reputation use case, we aim to innovate in the space, contribute back code, and operate as a testing ground.
|
|
67
63
|
|
|
68
|
-
The first prerequisite is to obtain an unspent transaction on a supporting
|
|
64
|
+
The first prerequisite is to obtain an unspent transaction on a supporting blockchain.
|
|
69
65
|
|
|
70
66
|
## Getting started
|
|
71
67
|
|
|
72
|
-
After you have obtained some
|
|
68
|
+
After you have obtained some blockchain currency, send those coins to an address for which you have the key pair. That becomes the genesis unspent transaction
|
|
73
69
|
|
|
74
70
|
Having created a genesis transaction, and recording the key pair safely, you are ready to start marking!
|
|
75
71
|
|
|
@@ -89,7 +85,7 @@ _Warning: do not use the default private key, that is set, in the script!_
|
|
|
89
85
|
|
|
90
86
|
Git mark will generate a new address to send to, a fee, an amount, a spending private key and unspent tx data as inputs to an rpc or a simple script `tx.sh` that lives in the bin directory. Future versions will use a transaction builder to send to a network directly
|
|
91
87
|
|
|
92
|
-
After running this script, an empty commit message is generated which you can check in, and points to the latest new unspent transaction, creating a two way link.
|
|
88
|
+
After running this script, an empty commit message is generated which you can check in, and points to the latest new unspent transaction, creating a two way link. The commit message is a gitmark [URI](./URI.md)
|
|
93
89
|
|
|
94
90
|
Congratulations! You have now marked your first git repo!
|
|
95
91
|
|
|
@@ -97,13 +93,15 @@ See also: [Example Workflow](./WORKFLOW.md)
|
|
|
97
93
|
|
|
98
94
|
## How it works
|
|
99
95
|
|
|
100
|
-
Gitmark simply uses [single
|
|
96
|
+
Gitmark simply uses [single-use seals](https://petertodd.org/2017/scalable-single-use-seal-asset-transfer) to tweak the initial public key address of the genesis transaction by the commit hash of the git tree. The current git hash is added to the original, genesis public key in the output transaction, creating a chain of commits in the blockchain.
|
|
97
|
+
|
|
98
|
+
In this way, the blockchain points to git. It then points the next commit back to the blockchain tx, creating a two-way link, and therefore, a strong binding at one particular point in time.
|
|
101
99
|
|
|
102
|
-
|
|
100
|
+
Similarly, the definitive git tree forms a chain of commits that go forward in time, and so do the new transactions on the blockchain. Further commits are periodically marked in time, proving an auditable trail on-chain of the evolution of the git tree. It also shows the latest confirmed state of a git tree that can be used for trading or in safe or smart contracts.
|
|
103
101
|
|
|
104
|
-
|
|
102
|
+
The first use case for gitmark is marking reputation trees, but it can be applied to any git system where the history is important.
|
|
105
103
|
|
|
106
|
-
|
|
104
|
+
For more technical information take a look at [SCHEMA.md](./SCHEMA.md).
|
|
107
105
|
|
|
108
106
|
## Recent git marks
|
|
109
107
|
|
|
@@ -133,6 +131,8 @@ The first use case for gitmark is marking of reputation trees, but it can be app
|
|
|
133
131
|
- [RGB](https://rgb-org.github.io/)
|
|
134
132
|
- [Commerce Block Mainstay](https://www.commerceblock.com/mainstay/) [[White Paper](https://cloudflare-ipfs.com/ipns/ipfs.commerceblock.com/commerceblock-whitepaper-mainstay.pdf)]
|
|
135
133
|
- [BIP 175 - Pay to Contract Protocol](https://github.com/bitcoin/bips/blob/master/bip-0175.mediawiki)
|
|
134
|
+
- [LRC-20](https://github.com/akitamiabtc/LRC-20/blob/main/LRC_20_V0.1.pdf)
|
|
135
|
+
- [DID-BTC](https://microstrategy.github.io/did-btc-spec/)
|
|
136
136
|
|
|
137
137
|
## Source code
|
|
138
138
|
|
|
@@ -148,9 +148,9 @@ The first use case for gitmark is marking of reputation trees, but it can be app
|
|
|
148
148
|
|
|
149
149
|
- The project git tree can be backed up or archived using git clone in multiple locations. It is natural that popular projects are cloned often in any case
|
|
150
150
|
|
|
151
|
-
- Seals can be opened and closed using a federation, in order to try out multiple consensus and
|
|
151
|
+
- Seals can be opened and closed using a federation, in order to try out multiple consensus and verification methods
|
|
152
152
|
|
|
153
|
-
- More robust verification frameworks can be built using node testing frameworks, and
|
|
153
|
+
- More robust verification frameworks can be built using node testing frameworks, and continuous integration, tho currently the distribution contains a git-mark-verify script
|
|
154
154
|
|
|
155
155
|
- Lightweight Autonomous Marking Agents (LAMAs) can be created that listen to communities for marks and just work, without needing a human operator. The service can be deployed on a server or container, and be designed to bring itself up if it goes down in any one location
|
|
156
156
|
|
package/SCHEMA.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
## Gitmark Schema
|
|
2
|
+
|
|
3
|
+
The Gitmark schema is a framework for organizing and tracking changes in a Git repository. It is inspired by the concept of [RGB Schemas](https://www.rgbfaq.com/glossary/schema-and-scripts/schema), which provide a way to represent and validate data in a decentralized network.
|
|
4
|
+
|
|
5
|
+
## Operations (OPS)
|
|
6
|
+
|
|
7
|
+
The Gitmark schema defines five core operations (OPS) for managing the state of a Git repository:
|
|
8
|
+
|
|
9
|
+
1. [GENESIS](./GENESIS.md): Initializes a new Git repository and creates the first commit.
|
|
10
|
+
2. [MARK](./MARK.md): Records a specific state or event in the Git repository, such as a milestone or release.
|
|
11
|
+
3. [TOPUP](./TOPUP.md): Adds new funds or resources to the Git repository, such as through a crowdfunding campaign or sponsorship.
|
|
12
|
+
4. [TRANSFER](./TRANSFER.md): Moves funds or resources from one part of the Git repository to another, such as between different branches or accounts.
|
|
13
|
+
5. [BURN](./BURN.md): Closes out a Git repository and transfers any remaining funds or resources to a designated recipient.
|
|
14
|
+
|
|
15
|
+
## Cryptographic Operations
|
|
16
|
+
|
|
17
|
+
The Gitmark schema uses cryptographic operations to ensure the integrity and security of the Git repository. These operations include:
|
|
18
|
+
|
|
19
|
+
1. Tweaking: A process for transitioning from one state to another in the Git repository, using cryptographic techniques to ensure that the transition is valid and secure.
|
|
20
|
+
2. Genesis: The creation of a new Git repository, using cryptographic techniques to establish the initial state and ensure that it is secure.
|
|
21
|
+
3. And other cryptographic operations as described in [CRYPTO](./CRYPTO.md)
|
|
22
|
+
|
|
23
|
+
The Gitmark schema maps each of these cryptographic operations to the corresponding OPS, ensuring that the Git repository is managed in a secure and consistent manner.
|
|
24
|
+
|
|
25
|
+
By following the Gitmark schema, developers can ensure that their Git repositories are well-organized, secure, and easy to manage. The schema provides a clear framework for tracking changes and managing resources, making it easier to collaborate and build high-quality software.
|
package/TOPUP.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
## Introduction
|
|
2
|
+
|
|
3
|
+
Topup is a function of Gitmark that lets you increase the amount of funds in a commitment. This allows a repo to continue operating after it has become low on funds.
|
|
4
|
+
|
|
5
|
+
## Add another tx input
|
|
6
|
+
|
|
7
|
+
Simply add another tx input to your transaction, and the new transaction output will have more funds in it.
|
|
8
|
+
|
|
9
|
+
## Donations
|
|
10
|
+
|
|
11
|
+
Certain versions of the software may incur donations; this, in turn, could be used to top up the project and keep it going longer.
|
|
12
|
+
|
|
13
|
+
## Conclusion
|
|
14
|
+
|
|
15
|
+
Topup is a super simple workflow that sweeps inputs into a new output spliced together with a mark, in order to increase funds on-chain.
|
package/USE_CASES.md
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# **Gitmark Use Cases**
|
|
2
|
+
|
|
3
|
+
Gitmark harnesses git and blockchain technologies to provide innovative solutions across various fields. Below is a detailed breakdown of its diverse applications:
|
|
4
|
+
|
|
5
|
+
### **1. Distributed Reputation Systems**
|
|
6
|
+
- **Purpose:** Create tamper-proof, transparent, decentralized reputation systems.
|
|
7
|
+
- **Benefits:** Enhances trust in digital interactions.
|
|
8
|
+
|
|
9
|
+
### **2. Distributed Ledgers**
|
|
10
|
+
- **Applications:** Financial transactions, supply chain management, etc.
|
|
11
|
+
- **Technology:** Combines git with blockchain for secure, decentralized ledgers.
|
|
12
|
+
|
|
13
|
+
### **3. Registries**
|
|
14
|
+
- **Function:** Establish decentralized registries for assets, identities, and more.
|
|
15
|
+
- **Advantages:** Ensures data integrity and accessibility.
|
|
16
|
+
|
|
17
|
+
### **4. Safe or Smart Contracts**
|
|
18
|
+
- **Capability:** Execute contracts stored on blockchain, ensuring transparency and immutability.
|
|
19
|
+
- **Feature:** Automated contract execution.
|
|
20
|
+
|
|
21
|
+
### **5. Asset Issuance**
|
|
22
|
+
- **Service:** Issue digital assets like tokens or certificates with a blockchain-backed history.
|
|
23
|
+
- **Highlight:** Provides verifiability and auditability.
|
|
24
|
+
|
|
25
|
+
### **6. Distributed Exchanges**
|
|
26
|
+
- **Description:** Build decentralized exchanges for peer-to-peer asset trading.
|
|
27
|
+
- **Characteristic:** Maintains a transparent and immutable transaction record.
|
|
28
|
+
|
|
29
|
+
### **7. Historical Reconstruction**
|
|
30
|
+
- **Functionality:** Reconstruct complete histories from genesis block.
|
|
31
|
+
- **Use:** Offers a comprehensive, verifiable record of transactions and changes.
|
|
32
|
+
|
|
33
|
+
### **8. Distributed Global Consensus**
|
|
34
|
+
- **Purpose:** Facilitate decentralized decision-making and governance.
|
|
35
|
+
- **Approach:** Enables distributed consensus on system states.
|
|
36
|
+
|
|
37
|
+
### **9. Decentralized Web Hosting**
|
|
38
|
+
- **Application:** Host websites in a decentralized manner, promoting resistance to censorship.
|
|
39
|
+
- **Benefit:** Eliminates single points of failure.
|
|
40
|
+
|
|
41
|
+
### **10. Archiving and Historical Analysis**
|
|
42
|
+
- **Features:** Archive data and navigate through historical records.
|
|
43
|
+
- **Use Case:** Provides insights and supports audits.
|
|
44
|
+
|
|
45
|
+
### **11. Distributed Identity and PKI**
|
|
46
|
+
- **Function:** Create decentralized identity systems and public key infrastructure.
|
|
47
|
+
- **Advantages:** Enhances security and privacy in digital interactions.
|
|
48
|
+
|
|
49
|
+
### **12. Federated Side Chains**
|
|
50
|
+
- **Objective:** Build interoperable side chains among different blockchains or git repositories.
|
|
51
|
+
- **Benefit:** Facilitates communication and interoperability.
|
|
52
|
+
|
|
53
|
+
### **13. Auditing**
|
|
54
|
+
- **Capability:** Audit entire histories of systems for transparency and accountability.
|
|
55
|
+
- **Contexts:** Useful in financial transactions, supply chain management, etc.
|
|
56
|
+
|
|
57
|
+
### **14. Fraud Detection**
|
|
58
|
+
- **Method:** Maintain a tamper-proof, auditable record to detect and prevent fraud.
|
|
59
|
+
- **Industries:** Applicable across various sectors.
|
|
60
|
+
|
|
61
|
+
### **15. Supply Chain Management**
|
|
62
|
+
- **Service:** Create traceable and accountable supply chain systems.
|
|
63
|
+
- **Feature:** Enhances transparency and efficiency throughout processes.
|
|
64
|
+
|
|
65
|
+
### **Conclusion**
|
|
66
|
+
Gitmark's integration of git and blockchain technologies offers transformative potential across industries, revolutionizing data storage, sharing, and verification to create more secure and efficient systems.
|
|
67
|
+
|
package/bin/git-mark-init
CHANGED
package/bin/git-mark.js
ADDED
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* git-mark — Anchor git commits to Bitcoin via blocktrails
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* git mark init [--chain tbtc4] [--voucher txo:...]
|
|
8
|
+
* git mark [--chain tbtc4]
|
|
9
|
+
* git mark info
|
|
10
|
+
* git mark verify
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { secp256k1, schnorr } from '@noble/curves/secp256k1';
|
|
14
|
+
import { sha256 } from '@noble/hashes/sha256';
|
|
15
|
+
import { bytesToHex, hexToBytes } from '@noble/hashes/utils';
|
|
16
|
+
import { execSync } from 'child_process';
|
|
17
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
|
|
18
|
+
import { join } from 'path';
|
|
19
|
+
|
|
20
|
+
// --- Constants ---
|
|
21
|
+
const SECP_N = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141n;
|
|
22
|
+
const TRAIL_FILE = 'blocktrails.json';
|
|
23
|
+
const PRIVATE_FILE = '.git/blocktrails.json';
|
|
24
|
+
const DEFAULT_CHAIN = 'tbtc4';
|
|
25
|
+
|
|
26
|
+
const CHAINS = {
|
|
27
|
+
tbtc3: { explorer: 'https://mempool.space/testnet/api', name: 'Bitcoin Testnet3' },
|
|
28
|
+
tbtc4: { explorer: 'https://mempool.space/testnet4/api', name: 'Bitcoin Testnet4' },
|
|
29
|
+
btc: { explorer: 'https://mempool.space/api', name: 'Bitcoin' },
|
|
30
|
+
signet: { explorer: 'https://mempool.space/signet/api', name: 'Bitcoin Signet' },
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// --- Blocktrails key chaining (BIP-341) ---
|
|
34
|
+
function taggedHash(tag, ...msgs) {
|
|
35
|
+
const tagHash = sha256(new TextEncoder().encode(tag));
|
|
36
|
+
return sha256(concatBytes(tagHash, tagHash, ...msgs));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function btScalar(pubkeyCompressed, state) {
|
|
40
|
+
const xOnly = pubkeyCompressed.slice(1);
|
|
41
|
+
const stateBytes = typeof state === 'string' ? new TextEncoder().encode(state) : state;
|
|
42
|
+
const sh = sha256(stateBytes);
|
|
43
|
+
return bytesToBigInt(taggedHash('TapTweak', xOnly, sh)) % SECP_N;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function bytesToBigInt(bytes) {
|
|
47
|
+
return BigInt('0x' + bytesToHex(bytes));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function bigIntToBytes(n) {
|
|
51
|
+
const hex = n.toString(16).padStart(64, '0');
|
|
52
|
+
return hexToBytes(hex);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function deriveChainedPrivkey(privkeyBytes, states) {
|
|
56
|
+
let d = bytesToBigInt(privkeyBytes);
|
|
57
|
+
let cur = new Uint8Array(secp256k1.getPublicKey(privkeyBytes, true));
|
|
58
|
+
for (const s of states) {
|
|
59
|
+
const t = btScalar(cur, s);
|
|
60
|
+
d = (d + t) % SECP_N;
|
|
61
|
+
cur = new Uint8Array(secp256k1.ProjectivePoint.BASE.multiply(d).toRawBytes(true));
|
|
62
|
+
}
|
|
63
|
+
return bigIntToBytes(d);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function deriveChainedPubkey(pubkeyBase, states) {
|
|
67
|
+
let P = secp256k1.ProjectivePoint.fromHex(bytesToHex(pubkeyBase));
|
|
68
|
+
let cur = pubkeyBase;
|
|
69
|
+
for (const s of states) {
|
|
70
|
+
const t = btScalar(cur, s);
|
|
71
|
+
P = P.add(secp256k1.ProjectivePoint.BASE.multiply(t));
|
|
72
|
+
cur = new Uint8Array(P.toRawBytes(true));
|
|
73
|
+
}
|
|
74
|
+
return cur;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// --- Bech32m encoding ---
|
|
78
|
+
const BECH32_CHARSET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l';
|
|
79
|
+
function polymod(values) {
|
|
80
|
+
const GEN = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3];
|
|
81
|
+
let chk = 1;
|
|
82
|
+
for (const v of values) { const b = chk >> 25; chk = ((chk & 0x1ffffff) << 5) ^ v; for (let i = 0; i < 5; i++) if ((b >> i) & 1) chk ^= GEN[i]; }
|
|
83
|
+
return chk;
|
|
84
|
+
}
|
|
85
|
+
function hrpExpand(hrp) { const r = []; for (const c of hrp) r.push(c.charCodeAt(0) >> 5); r.push(0); for (const c of hrp) r.push(c.charCodeAt(0) & 31); return r; }
|
|
86
|
+
function convertBits(data, from, to) { let acc = 0, bits = 0; const r = []; const max = (1 << to) - 1; for (const v of data) { acc = (acc << from) | v; bits += from; while (bits >= to) { bits -= to; r.push((acc >> bits) & max); } } if (bits > 0) r.push((acc << (to - bits)) & max); return r; }
|
|
87
|
+
|
|
88
|
+
function bech32mEncode(hrp, version, program) {
|
|
89
|
+
const values = [version, ...convertBits(program, 8, 5)];
|
|
90
|
+
const enc = [...hrpExpand(hrp), ...values, 0, 0, 0, 0, 0, 0];
|
|
91
|
+
const mod = polymod(enc) ^ 0x2bc830a3;
|
|
92
|
+
const checksum = [0, 1, 2, 3, 4, 5].map(i => (mod >> (5 * (5 - i))) & 31);
|
|
93
|
+
let result = hrp + '1';
|
|
94
|
+
for (const v of [...values, ...checksum]) result += BECH32_CHARSET[v];
|
|
95
|
+
return result;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function pubkeyToAddress(pubkeyHex, states, chain) {
|
|
99
|
+
const pubBytes = hexToBytes(pubkeyHex);
|
|
100
|
+
const derived = states.length > 0 ? deriveChainedPubkey(pubBytes, states) : pubBytes;
|
|
101
|
+
const xOnly = derived.slice(1);
|
|
102
|
+
const hrp = chain === 'btc' ? 'bc' : 'tb';
|
|
103
|
+
return bech32mEncode(hrp, 1, xOnly);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// --- P2TR script ---
|
|
107
|
+
function p2trScript(xonly) {
|
|
108
|
+
return new Uint8Array([0x51, 0x20, ...xonly]);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// --- Transaction building helpers ---
|
|
112
|
+
function writeVarInt(n) {
|
|
113
|
+
if (n < 0xfd) return new Uint8Array([n]);
|
|
114
|
+
if (n <= 0xffff) { const b = new Uint8Array(3); b[0] = 0xfd; b[1] = n & 0xff; b[2] = (n >> 8) & 0xff; return b; }
|
|
115
|
+
return new Uint8Array([0xfe, n & 0xff, (n >> 8) & 0xff, (n >> 16) & 0xff, (n >> 24) & 0xff]);
|
|
116
|
+
}
|
|
117
|
+
function writeU32LE(n) { const b = new Uint8Array(4); b[0] = n & 0xff; b[1] = (n >> 8) & 0xff; b[2] = (n >> 16) & 0xff; b[3] = (n >> 24) & 0xff; return b; }
|
|
118
|
+
function writeU64LE(n) { const b = new Uint8Array(8); let v = BigInt(n); for (let i = 0; i < 8; i++) { b[i] = Number(v & 0xffn); v >>= 8n; } return b; }
|
|
119
|
+
function concatBytes(...arrays) { const r = new Uint8Array(arrays.reduce((s, a) => s + a.length, 0)); let o = 0; for (const a of arrays) { r.set(a, o); o += a.length; } return r; }
|
|
120
|
+
function reverseTxid(txid) { return hexToBytes(txid).reverse(); }
|
|
121
|
+
|
|
122
|
+
function buildTransaction(input, outputs, privkeyBytes) {
|
|
123
|
+
const inputs = [input];
|
|
124
|
+
const internalXOnly = new Uint8Array(secp256k1.getPublicKey(privkeyBytes, true)).slice(1);
|
|
125
|
+
const untweakedHex = '5120' + bytesToHex(internalXOnly);
|
|
126
|
+
const needsTweak = bytesToHex(inputs[0].scriptPubKey) !== untweakedHex;
|
|
127
|
+
let signingKey = privkeyBytes;
|
|
128
|
+
if (needsTweak) {
|
|
129
|
+
const tweak = taggedHash('TapTweak', internalXOnly);
|
|
130
|
+
const t = bytesToBigInt(tweak);
|
|
131
|
+
let d = bytesToBigInt(privkeyBytes);
|
|
132
|
+
const fullPub = secp256k1.getPublicKey(privkeyBytes, false);
|
|
133
|
+
if (fullPub[64] & 1) d = SECP_N - d;
|
|
134
|
+
signingKey = bigIntToBytes((d + t) % SECP_N);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const version = 2, locktime = 0, sequence = 0xfffffffd;
|
|
138
|
+
const serOutputs = outputs.map(o =>
|
|
139
|
+
concatBytes(writeU64LE(o.amount), writeVarInt(o.scriptPubKey.length), o.scriptPubKey)
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
const shaPrevouts = sha256(concatBytes(...inputs.map(i => concatBytes(reverseTxid(i.txid), writeU32LE(i.vout)))));
|
|
143
|
+
const shaAmounts = sha256(concatBytes(...inputs.map(i => writeU64LE(i.amount))));
|
|
144
|
+
const shaScriptPubKeys = sha256(concatBytes(...inputs.map(i => concatBytes(writeVarInt(i.scriptPubKey.length), i.scriptPubKey))));
|
|
145
|
+
const shaSequences = sha256(concatBytes(...inputs.map(() => writeU32LE(sequence))));
|
|
146
|
+
const shaOutputs = sha256(concatBytes(...serOutputs));
|
|
147
|
+
|
|
148
|
+
const sigs = [];
|
|
149
|
+
for (let i = 0; i < inputs.length; i++) {
|
|
150
|
+
const sigMsg = concatBytes(
|
|
151
|
+
new Uint8Array([0x00, 0x00]),
|
|
152
|
+
writeU32LE(version), writeU32LE(locktime),
|
|
153
|
+
shaPrevouts, shaAmounts, shaScriptPubKeys, shaSequences, shaOutputs,
|
|
154
|
+
new Uint8Array([0x00]),
|
|
155
|
+
writeU32LE(i)
|
|
156
|
+
);
|
|
157
|
+
const sighash = taggedHash('TapSighash', sigMsg);
|
|
158
|
+
sigs.push(schnorr.sign(sighash, signingKey));
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const parts = [
|
|
162
|
+
writeU32LE(version),
|
|
163
|
+
new Uint8Array([0x00, 0x01]),
|
|
164
|
+
writeVarInt(inputs.length)
|
|
165
|
+
];
|
|
166
|
+
for (const inp of inputs) {
|
|
167
|
+
parts.push(reverseTxid(inp.txid), writeU32LE(inp.vout), new Uint8Array([0x00]), writeU32LE(sequence));
|
|
168
|
+
}
|
|
169
|
+
parts.push(writeVarInt(outputs.length));
|
|
170
|
+
for (const so of serOutputs) parts.push(so);
|
|
171
|
+
for (const sig of sigs) {
|
|
172
|
+
parts.push(new Uint8Array([0x01]), writeVarInt(sig.length), sig);
|
|
173
|
+
}
|
|
174
|
+
parts.push(writeU32LE(locktime));
|
|
175
|
+
return bytesToHex(concatBytes(...parts));
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async function broadcastTx(rawHex, explorer) {
|
|
179
|
+
const resp = await fetch(`${explorer}/tx`, { method: 'POST', body: rawHex, headers: { 'Content-Type': 'text/plain' } });
|
|
180
|
+
if (!resp.ok) throw new Error(`Broadcast failed: ${await resp.text()}`);
|
|
181
|
+
return (await resp.text()).trim();
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// --- Git helpers ---
|
|
185
|
+
function gitExec(cmd) { return execSync(cmd, { encoding: 'utf8' }).trim(); }
|
|
186
|
+
function getHead() { return gitExec('git rev-parse HEAD'); }
|
|
187
|
+
function getPrivkey() {
|
|
188
|
+
try { return gitExec('git config nostr.privkey'); } catch { return null; }
|
|
189
|
+
}
|
|
190
|
+
function setPrivkey(key) { gitExec(`git config --local nostr.privkey ${key}`); }
|
|
191
|
+
function isGitRoot() { return existsSync('.git'); }
|
|
192
|
+
|
|
193
|
+
// --- Trail file helpers ---
|
|
194
|
+
function loadTrail() {
|
|
195
|
+
if (!existsSync(TRAIL_FILE)) return null;
|
|
196
|
+
return JSON.parse(readFileSync(TRAIL_FILE, 'utf8'));
|
|
197
|
+
}
|
|
198
|
+
function saveTrail(trail) {
|
|
199
|
+
writeFileSync(TRAIL_FILE, JSON.stringify(trail, null, 2) + '\n');
|
|
200
|
+
}
|
|
201
|
+
function loadPrivateState() {
|
|
202
|
+
if (!existsSync(PRIVATE_FILE)) return null;
|
|
203
|
+
return JSON.parse(readFileSync(PRIVATE_FILE, 'utf8'));
|
|
204
|
+
}
|
|
205
|
+
function savePrivateState(state) {
|
|
206
|
+
writeFileSync(PRIVATE_FILE, JSON.stringify(state, null, 2) + '\n');
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// --- Parse TXO URI ---
|
|
210
|
+
function parseTxoUri(uri) {
|
|
211
|
+
const match = uri.match(/^txo:([^:]+):([a-f0-9]+):(\d+)/);
|
|
212
|
+
if (!match) throw new Error('Invalid TXO URI');
|
|
213
|
+
const params = new URLSearchParams(uri.split('?')[1] || '');
|
|
214
|
+
return { chain: match[1], txid: match[2], vout: parseInt(match[3]), amount: parseInt(params.get('amount')), key: params.get('key') };
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// --- Commands ---
|
|
218
|
+
async function cmdInit(args) {
|
|
219
|
+
if (!isGitRoot()) {
|
|
220
|
+
console.error('Error: .git/ not found in current directory. Run from a git repo root.');
|
|
221
|
+
process.exit(1);
|
|
222
|
+
}
|
|
223
|
+
if (existsSync(TRAIL_FILE) && !args.includes('--force')) {
|
|
224
|
+
console.error(`Error: ${TRAIL_FILE} already exists. Use --force to reinitialize.`);
|
|
225
|
+
process.exit(1);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Chain
|
|
229
|
+
const chainIdx = args.indexOf('--chain');
|
|
230
|
+
const chain = chainIdx !== -1 ? args[chainIdx + 1] : DEFAULT_CHAIN;
|
|
231
|
+
if (!CHAINS[chain]) { console.error(`Unknown chain: ${chain}`); process.exit(1); }
|
|
232
|
+
|
|
233
|
+
// Key
|
|
234
|
+
let privkey = getPrivkey();
|
|
235
|
+
let keySource = 'git config';
|
|
236
|
+
if (!privkey) {
|
|
237
|
+
const sk = secp256k1.utils.randomPrivateKey();
|
|
238
|
+
privkey = bytesToHex(sk);
|
|
239
|
+
setPrivkey(privkey);
|
|
240
|
+
keySource = 'generated';
|
|
241
|
+
}
|
|
242
|
+
const pubkey = bytesToHex(secp256k1.getPublicKey(hexToBytes(privkey), true));
|
|
243
|
+
|
|
244
|
+
// Create trail
|
|
245
|
+
const trail = {
|
|
246
|
+
version: '0.0.3',
|
|
247
|
+
profile: 'gitmark',
|
|
248
|
+
publicKeyBase: pubkey,
|
|
249
|
+
chain,
|
|
250
|
+
states: [],
|
|
251
|
+
txo: []
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
// Voucher funding
|
|
255
|
+
const voucherIdx = args.indexOf('--voucher');
|
|
256
|
+
if (voucherIdx !== -1) {
|
|
257
|
+
const voucherUri = args[voucherIdx + 1];
|
|
258
|
+
const txo = parseTxoUri(voucherUri);
|
|
259
|
+
if (!txo.key) { console.error('Voucher must include &key= parameter'); process.exit(1); }
|
|
260
|
+
if (!txo.amount) { console.error('Voucher must include &amount= parameter'); process.exit(1); }
|
|
261
|
+
|
|
262
|
+
const voucherKey = hexToBytes(txo.key);
|
|
263
|
+
const baseXonly = hexToBytes(pubkey).slice(1);
|
|
264
|
+
const baseScript = p2trScript(baseXonly);
|
|
265
|
+
|
|
266
|
+
// Fetch voucher scriptPubKey
|
|
267
|
+
const explorer = CHAINS[chain].explorer;
|
|
268
|
+
const txResp = await fetch(`${explorer}/tx/${txo.txid}`);
|
|
269
|
+
if (!txResp.ok) { console.error('Could not fetch voucher transaction'); process.exit(1); }
|
|
270
|
+
const txData = await txResp.json();
|
|
271
|
+
const prevOut = txData.vout?.[txo.vout];
|
|
272
|
+
if (!prevOut) { console.error(`Voucher output ${txo.vout} not found`); process.exit(1); }
|
|
273
|
+
|
|
274
|
+
const fee = 300;
|
|
275
|
+
const outputAmount = txo.amount - fee;
|
|
276
|
+
if (outputAmount <= 546) { console.error('Voucher too small'); process.exit(1); }
|
|
277
|
+
|
|
278
|
+
const rawTx = buildTransaction(
|
|
279
|
+
{ txid: txo.txid, vout: txo.vout, amount: txo.amount, scriptPubKey: hexToBytes(prevOut.scriptpubkey) },
|
|
280
|
+
[{ amount: outputAmount, scriptPubKey: baseScript }],
|
|
281
|
+
voucherKey
|
|
282
|
+
);
|
|
283
|
+
const newTxid = await broadcastTx(rawTx, explorer);
|
|
284
|
+
|
|
285
|
+
savePrivateState({ txid: newTxid, vout: 0, amount: outputAmount });
|
|
286
|
+
console.log(`Funded: ${outputAmount} sats (txid: ${newTxid})`);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
saveTrail(trail);
|
|
290
|
+
|
|
291
|
+
console.log(`Initialized gitmark trail: ${TRAIL_FILE}`);
|
|
292
|
+
console.log(`Key source: ${keySource}`);
|
|
293
|
+
console.log(`Base public key: ${pubkey}`);
|
|
294
|
+
console.log(`Chain: ${chain}`);
|
|
295
|
+
console.log(`Address: ${pubkeyToAddress(pubkey, [], chain)}`);
|
|
296
|
+
if (!existsSync(PRIVATE_FILE) && voucherIdx === -1) {
|
|
297
|
+
console.log(`\nUnfunded. Use: git mark init --voucher txo:${chain}:txid:vout?amount=X&key=Y`);
|
|
298
|
+
console.log(`Or send sats to: ${pubkeyToAddress(pubkey, [], chain)}`);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
async function cmdMark(args) {
|
|
303
|
+
const trail = loadTrail();
|
|
304
|
+
if (!trail) { console.error(`No ${TRAIL_FILE} found. Run: git mark init`); process.exit(1); }
|
|
305
|
+
const priv = loadPrivateState();
|
|
306
|
+
if (!priv) { console.error('No funding. Run: git mark init --voucher txo:...'); process.exit(1); }
|
|
307
|
+
|
|
308
|
+
const privkey = getPrivkey();
|
|
309
|
+
if (!privkey) { console.error('No private key. Set: git config nostr.privkey <hex>'); process.exit(1); }
|
|
310
|
+
|
|
311
|
+
const head = getHead();
|
|
312
|
+
const chain = trail.chain;
|
|
313
|
+
const explorer = CHAINS[chain]?.explorer;
|
|
314
|
+
if (!explorer) { console.error(`Unknown chain: ${chain}`); process.exit(1); }
|
|
315
|
+
|
|
316
|
+
// All previous states + current commit
|
|
317
|
+
const prevStates = [...trail.states];
|
|
318
|
+
const allStates = [...prevStates, head];
|
|
319
|
+
|
|
320
|
+
// Derive signing key (chained through previous states)
|
|
321
|
+
const signingKey = prevStates.length > 0
|
|
322
|
+
? deriveChainedPrivkey(hexToBytes(privkey), prevStates)
|
|
323
|
+
: hexToBytes(privkey);
|
|
324
|
+
|
|
325
|
+
// Derive next address (chained through all states including current)
|
|
326
|
+
const nextPub = deriveChainedPubkey(hexToBytes(trail.publicKeyBase), allStates);
|
|
327
|
+
const nextXonly = nextPub.slice(1);
|
|
328
|
+
const nextScript = p2trScript(nextXonly);
|
|
329
|
+
|
|
330
|
+
// Fetch current UTXO scriptPubKey
|
|
331
|
+
const txResp = await fetch(`${explorer}/tx/${priv.txid}`);
|
|
332
|
+
if (!txResp.ok) { console.error('Could not fetch current UTXO'); process.exit(1); }
|
|
333
|
+
const txData = await txResp.json();
|
|
334
|
+
const prevOut = txData.vout?.[priv.vout];
|
|
335
|
+
if (!prevOut) { console.error('Current UTXO not found'); process.exit(1); }
|
|
336
|
+
|
|
337
|
+
const fee = 300;
|
|
338
|
+
const outputAmount = priv.amount - fee;
|
|
339
|
+
if (outputAmount <= 546) { console.error('Trail UTXO too small. Fund with: git mark init --voucher ...'); process.exit(1); }
|
|
340
|
+
|
|
341
|
+
const rawTx = buildTransaction(
|
|
342
|
+
{ txid: priv.txid, vout: priv.vout, amount: priv.amount, scriptPubKey: hexToBytes(prevOut.scriptpubkey) },
|
|
343
|
+
[{ amount: outputAmount, scriptPubKey: nextScript }],
|
|
344
|
+
signingKey
|
|
345
|
+
);
|
|
346
|
+
const newTxid = await broadcastTx(rawTx, explorer);
|
|
347
|
+
|
|
348
|
+
// Update trail
|
|
349
|
+
trail.states.push(head);
|
|
350
|
+
trail.txo.push(`txo:${chain}:${newTxid}:0?commit=${head}`);
|
|
351
|
+
saveTrail(trail);
|
|
352
|
+
|
|
353
|
+
// Update private state
|
|
354
|
+
savePrivateState({ txid: newTxid, vout: 0, amount: outputAmount });
|
|
355
|
+
|
|
356
|
+
const address = pubkeyToAddress(trail.publicKeyBase, allStates, chain);
|
|
357
|
+
console.log(`Marked: ${head.slice(0, 8)} → ${newTxid.slice(0, 16)}...`);
|
|
358
|
+
console.log(`Address: ${address}`);
|
|
359
|
+
console.log(`Balance: ${outputAmount} sats`);
|
|
360
|
+
console.log(`TXO: txo:${chain}:${newTxid}:0?commit=${head}`);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
async function cmdInfo() {
|
|
364
|
+
const trail = loadTrail();
|
|
365
|
+
if (!trail) { console.error(`No ${TRAIL_FILE} found.`); process.exit(1); }
|
|
366
|
+
const priv = loadPrivateState();
|
|
367
|
+
|
|
368
|
+
console.log(`Profile: ${trail.profile}`);
|
|
369
|
+
console.log(`Version: ${trail.version}`);
|
|
370
|
+
console.log(`Chain: ${trail.chain}`);
|
|
371
|
+
console.log(`Base public key: ${trail.publicKeyBase}`);
|
|
372
|
+
console.log(`Base address: ${pubkeyToAddress(trail.publicKeyBase, [], trail.chain)}`);
|
|
373
|
+
console.log(`Marks: ${trail.states.length}`);
|
|
374
|
+
if (trail.states.length > 0) {
|
|
375
|
+
const currentAddr = pubkeyToAddress(trail.publicKeyBase, trail.states, trail.chain);
|
|
376
|
+
console.log(`Current address: ${currentAddr}`);
|
|
377
|
+
console.log(`Last commit: ${trail.states[trail.states.length - 1]}`);
|
|
378
|
+
console.log(`Last TXO: ${trail.txo[trail.txo.length - 1]}`);
|
|
379
|
+
}
|
|
380
|
+
if (priv) {
|
|
381
|
+
console.log(`Balance: ${priv.amount} sats`);
|
|
382
|
+
} else {
|
|
383
|
+
console.log('Balance: unfunded');
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
async function cmdVerify() {
|
|
388
|
+
const trail = loadTrail();
|
|
389
|
+
if (!trail) { console.error(`No ${TRAIL_FILE} found.`); process.exit(1); }
|
|
390
|
+
if (trail.states.length === 0) { console.log('No marks to verify.'); return; }
|
|
391
|
+
|
|
392
|
+
const chain = CHAINS[trail.chain];
|
|
393
|
+
if (!chain) { console.error(`Unknown chain: ${trail.chain}`); process.exit(1); }
|
|
394
|
+
|
|
395
|
+
console.log(`Verifying ${trail.states.length} mark(s)...`);
|
|
396
|
+
let ok = true;
|
|
397
|
+
|
|
398
|
+
for (let i = 0; i < trail.states.length; i++) {
|
|
399
|
+
const statesUpTo = trail.states.slice(0, i + 1);
|
|
400
|
+
const expectedAddr = pubkeyToAddress(trail.publicKeyBase, statesUpTo, trail.chain);
|
|
401
|
+
const txoUri = trail.txo[i];
|
|
402
|
+
const parsed = parseTxoUri(txoUri);
|
|
403
|
+
|
|
404
|
+
try {
|
|
405
|
+
const resp = await fetch(`${chain.explorer}/tx/${parsed.txid}`);
|
|
406
|
+
if (!resp.ok) { console.log(` [${i}] FAIL — tx not found: ${parsed.txid.slice(0, 16)}...`); ok = false; continue; }
|
|
407
|
+
const txData = await resp.json();
|
|
408
|
+
const out = txData.vout?.[parsed.vout];
|
|
409
|
+
if (!out) { console.log(` [${i}] FAIL — output ${parsed.vout} not found`); ok = false; continue; }
|
|
410
|
+
if (out.scriptpubkey_address === expectedAddr) {
|
|
411
|
+
console.log(` [${i}] OK — ${trail.states[i].slice(0, 8)} → ${expectedAddr.slice(0, 20)}...`);
|
|
412
|
+
} else {
|
|
413
|
+
console.log(` [${i}] FAIL — address mismatch`);
|
|
414
|
+
console.log(` expected: ${expectedAddr}`);
|
|
415
|
+
console.log(` got: ${out.scriptpubkey_address}`);
|
|
416
|
+
ok = false;
|
|
417
|
+
}
|
|
418
|
+
} catch (e) {
|
|
419
|
+
console.log(` [${i}] ERROR — ${e.message}`);
|
|
420
|
+
ok = false;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
console.log(ok ? '\nAll marks verified.' : '\nVerification failed.');
|
|
425
|
+
process.exit(ok ? 0 : 1);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// --- CLI ---
|
|
429
|
+
const args = process.argv.slice(2);
|
|
430
|
+
const cmd = args[0];
|
|
431
|
+
|
|
432
|
+
if (cmd === 'init') {
|
|
433
|
+
cmdInit(args.slice(1));
|
|
434
|
+
} else if (cmd === 'info') {
|
|
435
|
+
cmdInfo();
|
|
436
|
+
} else if (cmd === 'verify') {
|
|
437
|
+
cmdVerify();
|
|
438
|
+
} else if (cmd === 'mark' || !cmd || (cmd && !cmd.startsWith('-'))) {
|
|
439
|
+
// Default action is mark (git mark = git mark mark)
|
|
440
|
+
// But if no trail exists, suggest init
|
|
441
|
+
if (!existsSync(TRAIL_FILE) && cmd !== 'mark') {
|
|
442
|
+
console.log('Usage:');
|
|
443
|
+
console.log(' git mark init [--chain tbtc4] [--voucher txo:...]');
|
|
444
|
+
console.log(' git mark # anchor HEAD to Bitcoin');
|
|
445
|
+
console.log(' git mark info # show trail state');
|
|
446
|
+
console.log(' git mark verify # verify trail against Bitcoin');
|
|
447
|
+
} else {
|
|
448
|
+
cmdMark(args.slice(cmd === 'mark' ? 1 : 0));
|
|
449
|
+
}
|
|
450
|
+
} else {
|
|
451
|
+
console.log('Usage:');
|
|
452
|
+
console.log(' git mark init [--chain tbtc4] [--voucher txo:...]');
|
|
453
|
+
console.log(' git mark # anchor HEAD to Bitcoin');
|
|
454
|
+
console.log(' git mark info # show trail state');
|
|
455
|
+
console.log(' git mark verify # verify trail against Bitcoin');
|
|
456
|
+
}
|
package/package.json
CHANGED
|
@@ -1,40 +1,34 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gitmark",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.0.69",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Anchor git commits to Bitcoin via blocktrails",
|
|
6
|
+
"main": "bin/git-mark.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"git-mark": "bin/git-mark.js"
|
|
9
|
+
},
|
|
6
10
|
"scripts": {
|
|
7
|
-
"test": "
|
|
11
|
+
"test": "node bin/git-mark.js --help"
|
|
8
12
|
},
|
|
9
13
|
"repository": {
|
|
10
14
|
"type": "git",
|
|
11
15
|
"url": "git+https://github.com/solidpayorg/gitmark.git"
|
|
12
16
|
},
|
|
13
17
|
"keywords": [
|
|
14
|
-
"gitmark"
|
|
18
|
+
"gitmark",
|
|
19
|
+
"blocktrails",
|
|
20
|
+
"bitcoin",
|
|
21
|
+
"git",
|
|
22
|
+
"nostr"
|
|
15
23
|
],
|
|
16
|
-
"bin": {
|
|
17
|
-
"git-mark": "bin/git-mark",
|
|
18
|
-
"git-mark-init": "bin/git-mark-init",
|
|
19
|
-
"git-mark-list": "bin/git-mark-list",
|
|
20
|
-
"git-mark-verify": "bin/git-mark-verify"
|
|
21
|
-
},
|
|
22
24
|
"author": "Melvin Carvalho",
|
|
23
25
|
"license": "MIT",
|
|
24
26
|
"bugs": {
|
|
25
27
|
"url": "https://github.com/solidpayorg/gitmark/issues"
|
|
26
28
|
},
|
|
27
|
-
"homepage": "https://
|
|
29
|
+
"homepage": "https://git-mark.com",
|
|
28
30
|
"dependencies": {
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
"child_process": "^1.0.2",
|
|
32
|
-
"fs": "^0.0.1-security",
|
|
33
|
-
"gitlog": "^4.0.4",
|
|
34
|
-
"gitmark": "^0.0.56",
|
|
35
|
-
"minimist": "^1.2.5",
|
|
36
|
-
"sha256": "^0.2.0",
|
|
37
|
-
"tiny-secp256k1": "^1.1.6",
|
|
38
|
-
"ws": "^8.2.3"
|
|
31
|
+
"@noble/curves": "^1.2.0",
|
|
32
|
+
"@noble/hashes": "^1.3.0"
|
|
39
33
|
}
|
|
40
34
|
}
|