Skip to content

NIP-13 Nonce mining #46

@immetoo2

Description

@immetoo2

Nonce mining is slow on json object, on string is fast but on bytes is the fastest.
Have been trying to develop an nostr server, and looked at the event id mining.
Thus gave it a try as a unit test, and thought to share it as you may need equal like code;

	@Test
	public void testMining() throws Exception {
		Duration maxMining = Duration.ofSeconds(360);
		int maxDifficulty = (OctoTrust.KEY_LENGTH * 8) / 2; // is 128 thus 50% of bits are zero
		NoStrIdentity nid = new NoStrIdentity(NoStrIdentityPrivateKey.ofRandom());
		try (JsonReader reader = Json.createReader(new InputStreamReader(getClass().getResourceAsStream("note-text-simple.json")))) {
			NoStrEvent event = new NoStrEvent(reader.readObject());
			Assertions.assertTrue(NoStrEventSignature.verify(event));
			int testOffsetDifficulty = 10; // +12 from note-text-simple.json

			// use test public key and copy others
			NoStrEventPayload payload = new NoStrEventPayload(nid.getPublicKey(), event.getPayload().getCreatedAt(), event.getPayload().getKind(), event.getPayload().getTags(), event.getPayload().getContent());
			Optional<NoStrEventTag> nonceTagOpt = payload.findFirstByQName(NoStrImplEventTag.NONCE);
			if (nonceTagOpt.isEmpty()) {
				return; // no mining requested
			}
			// replace X NoStrEventTag impl or NoStrEventTagCustom or NoStrTagNonce with local tag for setter access
			NoStrTagNonce nonceTag = new NoStrTagNonce(Integer.parseInt(nonceTagOpt.get().getMetaArgument(NoStrImplTagNonce.DIFFICULTY).get()) + testOffsetDifficulty);
			int nonceTagIdx = payload.getTags().indexOf(nonceTagOpt.get());
			payload.getTags().set(nonceTagIdx, nonceTag);

			int targetDifficulty = nonceTag.getDifficulty();
			if (targetDifficulty > maxDifficulty) {
				throw new IllegalArgumentException("Target difficulty can't be larger than " + maxDifficulty);
			}
			int mineCnt = 0;
			byte[] eventId = null;
			int leadingZeroBits = 0;
			byte[] nowStr = Long.toString(payload.getCreatedAt().getEpochSecond()).getBytes(StandardCharsets.UTF_8);
			byte[] payloadStr = NoStrEventSignature.generateEventIdJsonString(payload).getBytes(StandardCharsets.UTF_8);
			int createdAtLength = nowStr.length;
			int createdAtIdx = indexOf(payloadStr, nowStr, indexOf(payloadStr, new byte[]{'\"'}, 5));
			int nonceProofIdx = indexOf(payloadStr, "\"nonce\"".getBytes(StandardCharsets.UTF_8), 0) + 9;// ["nonce","7539","12"]],
			int nonceProofLength = Integer.toString(nonceTag.getBearerProof()).getBytes(StandardCharsets.UTF_8).length;
			long createdAtTime = System.currentTimeMillis();
			long maxMiningTime = createdAtTime + maxMining.toMillis();
			MessageDigest sha256 = OctoTrustHash.sha256Algorithm();

			while (leadingZeroBits < targetDifficulty) {
				mineCnt++;
				createdAtTime = System.currentTimeMillis();
				if (createdAtTime > maxMiningTime) {
					throw new IllegalStateException("Mining resource limit exceeded");
				}
				long createdAtTimeSecs = createdAtTime / 1000;
				int createdAtTimeDigits = numDigits(createdAtTimeSecs);
				if (createdAtLength != createdAtTimeDigits) {
					createdAtLength = createdAtTimeDigits;
					payloadStr = growBySplit(payloadStr, createdAtIdx);
				}
				writeDigits(createdAtTimeSecs, createdAtTimeDigits, payloadStr, createdAtIdx);

				int nonceProofDigits = numDigits(mineCnt);
				if (nonceProofLength != nonceProofDigits) {
					nonceProofLength = nonceProofDigits;
					payloadStr = growBySplit(payloadStr, nonceProofIdx);
				}
				writeDigits(mineCnt, nonceProofDigits, payloadStr, nonceProofIdx);

				eventId = sha256.digest(payloadStr);
				leadingZeroBits = 0;
				for (int i = 0; i < eventId.length; i++) {
					int step = eventId[i] & 0xFF; // force unsigned
					if (step == 0) {
						leadingZeroBits += 8;
						continue;
					}
					leadingZeroBits += Integer.numberOfLeadingZeros(step) - 24;
					break;
				}
			}
			payload.setCreatedAt(Instant.ofEpochMilli(createdAtTime));
			nonceTag.setBearerProof(mineCnt);

			NoStrEvent eventMined = NoStrEventSignature.sign(payload, nid.getPrivateKey(), eventId);

			System.out.println("idOrg=" + event.getId().getHex());
			System.out.println("idHex=" + eventMined.getId().getHex());
			System.out.println("idStr=" + NoStrEventSignature.generateEventIdJsonString(payload));
			System.out.println("leadZero=" + leadingZeroBits);
			System.out.println("mineCnt=" + mineCnt);
			System.out.println("idStrHex=" + OctoBitFormat.HEX.fromBytes(NoStrEventSignature.generateEventIdJsonString(payload).getBytes(StandardCharsets.UTF_8)));
			System.out.println("idStrArr=" + OctoBitFormat.HEX.fromBytes(payloadStr));

			Assertions.assertTrue(NoStrEventSignature.verify(eventMined));
			Assertions.assertEquals("" + mineCnt, eventMined.getPayload().findFirstByQName("nonce").get().getMetaArgument(0).get());
			Assertions.assertEquals("" + targetDifficulty, eventMined.getPayload().findFirstByQName("nonce").get().getMetaArgument(1).get());
		}
	}

	public byte[] growBySplit(byte[] src, int splitIdx) {
		byte[] result = new byte[src.length + 1];
		System.arraycopy(src, 0, result, 0, splitIdx);
		System.arraycopy(src, splitIdx + 1, result, splitIdx + 2, src.length - splitIdx - 1);
		return result;
	}
	
	public void writeDigits(long value, int digits, byte[] target, int off) {
		int i = digits - 1;
		while (value > 0) {
			target[i+off] = (byte) ((value % 10) + '0');
			value /= 10;
			i--;
		}
	}

	public int numDigits(long n) {
		return (int) Math.log10(n) + 1;
	}
	
	public int indexOf(byte[] source, byte[] target, int fromIndex) {
		return indexOf(source, 0, source.length, target, 0, target.length, fromIndex);
	}

	public int indexOf(byte[] source, int sourceOffset, int sourceCount, byte[] target, int targetOffset, int targetCount, int fromIndex) {
		if (fromIndex >= sourceCount) {
			return (targetCount == 0 ? sourceCount : -1);
		}
		if (fromIndex < 0) {
			fromIndex = 0;
		}
		if (targetCount == 0) {
			return fromIndex;
		}
		byte first = target[targetOffset];
		int max = sourceOffset + (sourceCount - targetCount);
		for (int i = sourceOffset + fromIndex; i <= max; i++) {
			if (source[i] != first) { // search first
				while (++i <= max && source[i] != first) {
					;
				}
			}
			if (i <= max) { // search left over
				int j = i + 1;
				int end = j + targetCount - 1;
				for (int k = targetOffset + 1; j < end && source[j] == target[k]; j++, k++) {
					;
				}
				if (j == end) {
					return i - sourceOffset;
				}
			}
		}
		return -1;
	}

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions